Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
29c666c
added current_account entities
goncalopinto1 Mar 9, 2026
62c16d2
started creating the fetcher and parser for current account information
goncalopinto1 Mar 11, 2026
ce87035
changed parser calls on fetcher
goncalopinto1 Mar 11, 2026
aa6dcc0
added current account provider
goncalopinto1 Mar 12, 2026
96dec31
created unpaid table
goncalopinto1 Mar 13, 2026
81d7e8a
added transactions and account statement tables
goncalopinto1 Mar 13, 2026
96ceef8
fix: fetching and parsing issues
goncalopinto1 Mar 14, 2026
edde8d9
somes fixes
goncalopinto1 Mar 17, 2026
5248297
fixed problem with tables having diferent ids
goncalopinto1 Mar 18, 2026
e4a0baf
format
goncalopinto1 Mar 18, 2026
359a5e5
started current account page construction
goncalopinto1 Mar 28, 2026
af21e57
feat: added transaction cards
goncalopinto1 Mar 31, 2026
9fd3ab9
feat: added balance deadline and print balance to current account page
goncalopinto1 Mar 31, 2026
3e0d9c5
added text to l10n and applied Themes on current account page
goncalopinto1 Apr 1, 2026
824c7a4
feat: added filter
goncalopinto1 Apr 1, 2026
ffef3a4
changed order in tuition fees
goncalopinto1 Apr 1, 2026
9a343cf
format
goncalopinto1 Apr 1, 2026
32d276d
restored value on url index
goncalopinto1 Apr 1, 2026
ebd7996
Merge branch 'develop' into feature/current_account
goncalopinto1 Apr 1, 2026
ac20792
correct the position of values in transaction card and parser for int…
goncalopinto1 Apr 2, 2026
c1b64bc
feat: added onNullContent widgets and shimmers
goncalopinto1 Apr 2, 2026
1785e05
feat: added paymemt button redirection to up's payment page
goncalopinto1 Apr 2, 2026
a570d72
format and some fixes
goncalopinto1 Apr 2, 2026
4cd4bf6
fix: fix loading state when opening payment web page
goncalopinto1 Apr 12, 2026
3aafc8d
format
goncalopinto1 Apr 12, 2026
69041f3
fix: resolve l10n conflicts by regenerating
goncalopinto1 Apr 12, 2026
8dd33ba
format :/
goncalopinto1 Apr 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion packages/uni_app/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -360,10 +360,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
Expand Down Expand Up @@ -702,14 +706,15 @@
ASSETCATALOG_COMPILER_APPICON_NAME = uni_dev;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 9D8RT43WU8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = pt.up.fe.ni.uni.dev;
PRODUCT_BUNDLE_IDENTIFIER = pt.up.fe.ni.uni.dev.g;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
Expand Down
5 changes: 5 additions & 0 deletions packages/uni_app/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSCalendarsUsageDescription</key>
<string>Exportar exames e eventos para o calendário</string>
<key>NSPhotoLibraryUsageDescription</key>
Expand Down
15 changes: 15 additions & 0 deletions packages/uni_app/lib/controller/fetchers/fees_fetcher.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:http/http.dart';
import 'package:uni/controller/fetchers/session_dependant_fetcher.dart';
import 'package:uni/controller/networking/network_router.dart';
import 'package:uni/controller/parsers/parser_current_account.dart';
import 'package:uni/model/entities/current_account.dart';
import 'package:uni/session/flows/base/session.dart';

class FeesFetcher implements SessionDependantFetcher {
Expand All @@ -19,4 +21,17 @@ class FeesFetcher implements SessionDependantFetcher {
final query = {'pct_cod': session.username};
return NetworkRouter.getWithCookies(url, query, session);
}

Future<(List<Unpaid>, List<AccountStatement>)> extractCurrentAccount(
Session session,
CurrentAccountParser parser,
) async {
final response = await getUserFeesResponse(session);

final unpaid = parser.parseUnpaid(response);

final accountStatement = parser.parseAccountStatement(response);

return (unpaid, accountStatement);
}
}
137 changes: 137 additions & 0 deletions packages/uni_app/lib/controller/parsers/parser_current_account.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import 'package:html/dom.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:uni/model/entities/current_account.dart';

class CurrentAccountParser {
static const tabNames = {
'unpaid': ['Despesas não saldadas', 'Unpaid expenses'],
//'certificate': ['Certidão', 'Certidão'],
//'latePayment': ['Juros de mora Propinas', 'Juros de mora Propinas'],
//'tuitionFees': ['Propinas', 'Tuition fees'],
//'schoolInsurance': ['Seguro Escolar', 'Seguro Escolar'],
'accountStatement': ['Extrato Geral', 'Account Statement'],
};

String? findTableId(Document document, List<String> names) {
final tabs = document.querySelectorAll(
'#GPAG_CCORRENTE_GERAL_CONTA_CORRENTE_VIEW ul li a',
);
for (final tab in tabs) {
if (names.contains(tab.text.trim())) {
return tab.attributes['href'];
}
}
return null;
}

List<Unpaid> parseUnpaid(Response response) {
final List<Unpaid> data = [];
final document = parse(response.body);

final tableId = findTableId(document, tabNames['unpaid']!);

if (tableId != null) {
final tab = document.querySelector(tableId);
final rows = tab?.querySelectorAll('tr').skip(1) ?? [];

for (final row in rows) {
final cells = row.querySelectorAll('td');
final description = cells[2].text.trim();

final date = DateTime.parse(cells[3].text.trim());

final deadline = cells[4].text.trim().isEmpty
? null
: DateTime.parse(cells[4].text.trim());
final value = parseAmount(cells[5].text.trim()) ?? 0;
final amountDue = parseAmount(cells[7].text.trim()) ?? 0;

final anchor = cells[8].querySelector('a');
final relativeLink = anchor?.attributes['href'];

String? paymentLink;
if (relativeLink != null) {
paymentLink = 'https://sigarra.up.pt/feup/pt/$relativeLink';
}

final interest = cells[9].text.trim();
double? interestOnLatePayment;

if (interest.isNotEmpty) {
final parts = interest.split('+');
interestOnLatePayment = parts
.map((p) => parseAmount(p) ?? 0.0)
.reduce((a, b) => a + b);
}

data.add(
Unpaid(
description: description,
date: date,
deadline: deadline,
value: value,
amountDue: amountDue,
interestOnLatePayment: interestOnLatePayment,
paymentLink: paymentLink,
),
);
}
}
return data;
}

List<AccountStatement> parseAccountStatement(Response response) {
final document = parse(response.body);
final List<AccountStatement> data = [];

final tableId = findTableId(document, tabNames['accountStatement']!);

if (tableId != null) {
final tab = document.querySelector(tableId);
final rows = tab?.querySelectorAll('tbody tr') ?? [];

for (final row in rows) {
final cells = row.querySelectorAll('td');
final description = cells[0].text.trim();

final date = DateTime.parse(cells[1].text.trim());
final credit = parseAmount(cells[3].text.trim());

if (credit != null) {
data.add(
AccountStatement(
description: description,
date: date,
credit: credit,
),
);
}
}
}

return data;
}

String parseStatus(Element cell) {
final img = cell.querySelector('img');
final alt = img?.attributes['alt'] ?? '';

return switch (alt) {
'Pago' => 'Paid',
'Não pago mas prazo ainda não foi excedido' => 'unpaid',
'Não pago mas prazo foi excedido' => 'overdue',
_ => 'unknown',
};
}

double? parseAmount(String text) {
final cleaned = text
.replaceAll('€', '')
.replaceAll('\u00a0', '')
.replaceAll(' ', '')
.replaceAll(',', '');

return double.tryParse(cleaned);
}
}
45 changes: 44 additions & 1 deletion packages/uni_app/lib/generated/intl/messages_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class MessageLookup extends MessageLookupByLibrary {
"${Intl.plural(time, zero: 'Refreshed ${time} minutes ago', one: 'Refreshed ${time} minute ago', other: 'Refreshed ${time} minutes ago')}";

static m3(title) =>
"${Intl.select(title, {'horario': 'Schedule', 'exames': 'Exams', 'area': 'Personal Area', 'cadeiras': 'Course Units', 'autocarros': 'Buses', 'locais': 'Places', 'restaurantes': 'Restaurants', 'calendario': 'Calendar', 'biblioteca': 'Library', 'percurso_academico': 'Academic Path', 'mapa': 'Map', 'faculdade': 'Faculty', 'other': 'Other'})}";
"${Intl.select(title, {'horario': 'Schedule', 'exames': 'Exams', 'area': 'Personal Area', 'cadeiras': 'Course Units', 'autocarros': 'Buses', 'locais': 'Places', 'restaurantes': 'Restaurants', 'calendario': 'Calendar', 'biblioteca': 'Library', 'percurso_academico': 'Academic Path', 'mapa': 'Map', 'faculdade': 'Faculty', 'conta_corrente': 'Current Account', 'other': 'Other'})}";

static m4(period) =>
"${Intl.select(period, {'lunch': 'Lunch', 'dinner': 'Dinner', 'other': 'Other'})}";
Expand Down Expand Up @@ -71,6 +71,9 @@ class MessageLookup extends MessageLookupByLibrary {
),
"average": MessageLookupByLibrary.simpleMessage("Average"),
"balance": MessageLookupByLibrary.simpleMessage("Balance"),
"balance_description": MessageLookupByLibrary.simpleMessage(
"Your total outstanding balance",
),
"banner_info": MessageLookupByLibrary.simpleMessage(
"We collect anonymous usage data to help improve your experience. You can opt out anytime in the settings.",
),
Expand Down Expand Up @@ -146,6 +149,10 @@ class MessageLookup extends MessageLookupByLibrary {
"course_class": MessageLookupByLibrary.simpleMessage("Classes"),
"course_info": MessageLookupByLibrary.simpleMessage("Info"),
"courses": MessageLookupByLibrary.simpleMessage("Courses"),
"current_account": MessageLookupByLibrary.simpleMessage("Current Account"),
"current_account_description": MessageLookupByLibrary.simpleMessage(
"Track your fees, due dates and payment history.",
),
"current_state": MessageLookupByLibrary.simpleMessage("Current state: "),
"current_year": MessageLookupByLibrary.simpleMessage(
"Current academic year: ",
Expand Down Expand Up @@ -173,6 +180,7 @@ class MessageLookup extends MessageLookupByLibrary {
"drag_and_drop": MessageLookupByLibrary.simpleMessage(
"Drag and drop elements",
),
"due_in": MessageLookupByLibrary.simpleMessage("Due in"),
"ects": MessageLookupByLibrary.simpleMessage("ECTS performed: "),
"edit_homepage": MessageLookupByLibrary.simpleMessage("Edit"),
"edit_off": MessageLookupByLibrary.simpleMessage("Edit"),
Expand All @@ -199,6 +207,9 @@ class MessageLookup extends MessageLookupByLibrary {
"failed_upload": MessageLookupByLibrary.simpleMessage("Failed to upload"),
"favorite_filter": MessageLookupByLibrary.simpleMessage("Favorites"),
"fee_date": MessageLookupByLibrary.simpleMessage("Deadline"),
"fee_date_description": MessageLookupByLibrary.simpleMessage(
"Due date for your next payment",
),
"fee_notification": MessageLookupByLibrary.simpleMessage("Fee deadline"),
"feedback_description": MessageLookupByLibrary.simpleMessage(
"Report an issue or suggest an improvement",
Expand All @@ -211,6 +222,7 @@ class MessageLookup extends MessageLookupByLibrary {
"floors": MessageLookupByLibrary.simpleMessage("Floors"),
"forgot_password": MessageLookupByLibrary.simpleMessage("Forgot password?"),
"frequency": MessageLookupByLibrary.simpleMessage("Eligibility for exams"),
"general_history": MessageLookupByLibrary.simpleMessage("General History"),
"generate_reference": MessageLookupByLibrary.simpleMessage(
"Generate reference",
),
Expand All @@ -226,6 +238,9 @@ class MessageLookup extends MessageLookupByLibrary {
"increment": MessageLookupByLibrary.simpleMessage("Increment 1,00€"),
"instructor": MessageLookupByLibrary.simpleMessage("Instructor"),
"instructors": MessageLookupByLibrary.simpleMessage("Instructors"),
"interest_on_late_payments": MessageLookupByLibrary.simpleMessage(
"Interest on late payments",
),
"internet_status_exception": MessageLookupByLibrary.simpleMessage(
"Check your internet connection",
),
Expand Down Expand Up @@ -305,6 +320,9 @@ class MessageLookup extends MessageLookupByLibrary {
"no_courses_description": MessageLookupByLibrary.simpleMessage(
"Try to refresh the page",
),
"no_current_account_info": MessageLookupByLibrary.simpleMessage(
"Try refreshing the page or check back later.",
),
"no_data": MessageLookupByLibrary.simpleMessage(
"There is no data to show at this time",
),
Expand All @@ -326,6 +344,12 @@ class MessageLookup extends MessageLookupByLibrary {
"no_files_label": MessageLookupByLibrary.simpleMessage(
"You have nothing to see!",
),
"no_history_label": MessageLookupByLibrary.simpleMessage(
"Nothing here yet",
),
"no_history_sublabel": MessageLookupByLibrary.simpleMessage(
"Your payment history will appear here.",
),
"no_info": MessageLookupByLibrary.simpleMessage(
"There is no information to display",
),
Expand All @@ -349,12 +373,17 @@ class MessageLookup extends MessageLookupByLibrary {
),
"no_name_course": MessageLookupByLibrary.simpleMessage("Unnamed course"),
"no_news": MessageLookupByLibrary.simpleMessage("No news to display"),
"no_pending_label": MessageLookupByLibrary.simpleMessage("All caught up!"),
"no_pending_sublabel": MessageLookupByLibrary.simpleMessage(
"You have no pending payments.",
),
"no_places_info": MessageLookupByLibrary.simpleMessage(
"There is no information available about places",
),
"no_print_info": MessageLookupByLibrary.simpleMessage(
"No print balance information",
),
"no_records": MessageLookupByLibrary.simpleMessage("No records"),
"no_references": MessageLookupByLibrary.simpleMessage(
"There are no references to pay",
),
Expand All @@ -374,6 +403,12 @@ class MessageLookup extends MessageLookupByLibrary {
"no_trips": MessageLookupByLibrary.simpleMessage(
"No trips found at the moment",
),
"no_tuition_fees_label": MessageLookupByLibrary.simpleMessage(
"No tuition fees found",
),
"no_tuition_fees_sublabel": MessageLookupByLibrary.simpleMessage(
"Your tuition fee records will appear here.",
),
"notifications": MessageLookupByLibrary.simpleMessage("Notifications"),
"now": MessageLookupByLibrary.simpleMessage("Now"),
"occurrence_type": MessageLookupByLibrary.simpleMessage(
Expand All @@ -384,6 +419,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Error opening the file",
),
"other_links": MessageLookupByLibrary.simpleMessage("Other links"),
"overview": MessageLookupByLibrary.simpleMessage("Overview"),
"pass_change_request": MessageLookupByLibrary.simpleMessage(
"For security reasons, passwords must be changed periodically.",
),
Expand All @@ -397,6 +433,7 @@ class MessageLookup extends MessageLookupByLibrary {
"pendent_references": MessageLookupByLibrary.simpleMessage(
"Pending references",
),
"pending": MessageLookupByLibrary.simpleMessage("Pending"),
"permission_denied": MessageLookupByLibrary.simpleMessage(
"Permission denied",
),
Expand All @@ -406,6 +443,9 @@ class MessageLookup extends MessageLookupByLibrary {
"press_again": MessageLookupByLibrary.simpleMessage("Press again to exit"),
"print": MessageLookupByLibrary.simpleMessage("Print"),
"print_balance": MessageLookupByLibrary.simpleMessage("Print balance"),
"print_balance_description": MessageLookupByLibrary.simpleMessage(
"Funds for university printing services",
),
"prints": MessageLookupByLibrary.simpleMessage("Prints"),
"problem_id": MessageLookupByLibrary.simpleMessage(
"Brief identification of the problem",
Expand Down Expand Up @@ -468,17 +508,20 @@ class MessageLookup extends MessageLookupByLibrary {
"theme": MessageLookupByLibrary.simpleMessage("Theme"),
"title": MessageLookupByLibrary.simpleMessage("Title"),
"tomorrows_meals": MessageLookupByLibrary.simpleMessage("Tomorrow\'s Menu"),
"transactions": MessageLookupByLibrary.simpleMessage("Transactions"),
"try_again": MessageLookupByLibrary.simpleMessage("Try again"),
"try_different_login": MessageLookupByLibrary.simpleMessage(
"Having trouble signing in?",
),
"tuition_fees": MessageLookupByLibrary.simpleMessage("Tuition Fees"),
"uc_info": MessageLookupByLibrary.simpleMessage("Open UC page"),
"ucs": MessageLookupByLibrary.simpleMessage("UCS"),
"unable_to_load_data": MessageLookupByLibrary.simpleMessage(
"Unable to load data",
),
"unavailable": MessageLookupByLibrary.simpleMessage("Unavailable"),
"until": MessageLookupByLibrary.simpleMessage("Until"),
"upcoming_due": MessageLookupByLibrary.simpleMessage("Upcoming Due"),
"valid_email": MessageLookupByLibrary.simpleMessage(
"Please enter a valid email",
),
Expand Down
Loading
Loading