Tattoo Logo

Tattoo

A modern reimplementation of TAT — the course helper app for Taipei Tech students

An NPC Project Made with Flutter Works on my machine Weblate Translation Progress

Help us translate! We use Weblate to manage localizations. Join the project and help us bring Tattoo to your language!

What is this?

Project Tattoo is a work-in-progress Flutter app that helps Taipei Tech (NTUT) students access their course schedules and academic information. This is a ground-up reimplementation with a focus on:

  • Modern, maintainable code — Clean architecture and best practices
  • Developer-friendly — Easy for future club members to understand and contribute
  • Concise implementation — No unnecessary complexity

Getting Started

This project uses mise to manage development tools (Flutter, Java, Ruby).

# Install and activate mise (if not already)
# See: https://mise.jdx.dev/getting-started.html

# Install Flutter, Java, and Ruby
mise install

# Install Flutter dependencies
flutter pub get

# (Optional) Install Ruby dependencies for fastlane
bundle install

# Run the app
flutter run

Firebase & Credentials

This project uses a private Git repository to manage sensitive credentials (signing keys, service accounts, and Firebase configuration files).

  1. Request Access: Contact the maintainers for access to the tattoo-credentials repository.
  2. Configure .env: Copy .env.example to .env and fill in the MATCH_GIT_URL and MATCH_PASSWORD.
  3. Fetch Configs: Run dart run tool/credentials.dart fetch. The tool decrypts the staging and production Firebase configurations directly into their flavor-specific paths and installs the shared Android keystore and service account.

Firebase Setup (Maintainers only)

If you need to reconfigure Firebase:

  1. Install the Firebase CLI.
  2. Install the FlutterFire CLI.
  3. Run FlutterFire CLI separately for each flavor, writing Dart output to lib/firebase_options_staging.dart or lib/firebase_options_production.dart and supplying the matching native identifiers, output paths, and iOS build configuration. Do not overwrite the handwritten lib/firebase_options.dart selector.
  4. Encrypt each native Firebase file to its matching path under firebase/staging/ or firebase/production/ with dart run tool/credentials.dart encrypt <file> <path_in_repo>.

Build Flavors

Tattoo has two native Flutter flavors:

Flavor Android application ID iOS bundle ID Name and icon
staging club.ntut.tattoo club.ntut.tattoo Tattoo, outlined wireframe T
production club.ntut.npc.tat com.npc.tatFlutter TAT, filled T

Staging is the default flavor. Select production explicitly:

flutter run --flavor staging
flutter build appbundle --flavor production
flutter build ipa --flavor production

PR previews and daily releases always build staging; the production release workflow always builds production.

Campus Wi-Fi

Campus Wi-Fi setup supports Android and physical iPhones/iPads under the showWifiButton flag. It uses securely saved portal credentials; demo mode has no saved password.

Setup and compatibility-save errors show failure feedback and leave the assistant available for retry.

  • Android: network suggestions, explicit compatibility-mode saving, and manual setup. Denied suggestion permission offers Wi-Fi Control Permission, never automatic compatibility saving.
  • iOS: user-approved persistent PEAP configuration with system certificate trust restricted to NTUT server names. Credential changes require renewed consent; Apple's API leaves PEAP inner authentication to negotiation.
  • Removal: available without signing in; removes only app-owned configurations and disables automatic credential refresh, even on failure. Cancel changes nothing. Android compatibility/manual networks must be forgotten in system Wi-Fi settings; iOS acknowledges a removal request, not confirmed deletion.
  • Verification: accepted setup does not prove connectivity. Check Settings > Wi-Fi on campus. iOS has no public Wi-Fi settings shortcut, and simulator configuration is unsupported.

Both iOS App IDs and signing profiles need Hotspot Configuration (ios/Runner/Runner.entitlements). Physical-device authentication and certificate validation require on-campus verification. See AGENTS.md for the shared repository and OS-selected provisioner architecture.

HTML Snapshot Capture

Developers can capture raw NTUT HTML/XML responses for parser work:

# List supported Service-layer capture presets
dart run tool/html_snapshot.dart list

# Capture a known page using test/test_config.json
dart run tool/html_snapshot.dart capture student_query.profile -m "profile parser baseline"

# Capture multiple known pages
dart run tool/html_snapshot.dart capture student_query.profile course.semester_list -m "semester start samples"

# Capture every preset that can be resolved without explicit IDs
dart run tool/html_snapshot.dart capture -a -m "routine parser refresh"

# Capture a custom request with the same service client settings
dart run tool/html_snapshot.dart raw QryBasisData.jsp --service student_query -m "custom profile page check"

The tool reads test/test_config.json. If the file is missing, copy test/test_config.json.example to test/test_config.json and fill in NTUT_TEST_USERNAME and NTUT_TEST_PASSWORD.

Captured files are written under tmp/html_snapshot/ with the service name prefixed in the file name. Each file starts with a commented metadata block containing a raw-capture warning, preset, request_url, fetchtime, message, and a parser expected-result TODO; use -m/--message to describe why the sample was kept. If no message is provided, the tool writes a message: TODO placeholder that must be replaced before promotion. The parser expected-result TODO is separate from message and may remain until the HTML-based test code is complete. This directory is local-only and ignored by git. Do not commit raw captures; de-identify them before promoting any sample into a fixture or documentation, and do not submit a promoted snapshot without a meaningful message. capture -a skips presets that require explicit identifiers, such as a course, teacher, classroom, or syllabus ID.

When adding new Service-layer parser requests, add or update capture presets in tool/html_snapshot/presets.dart.

Portal Endpoints Scraper

Developers can scrape all available subsystem endpoints and their corresponding apOu SSO codes from the NTUT portal:

# Scrape and print as plain text table using test/test_config.json
dart run tool/scrape_portal.dart

# Scrape and print as formatted JSON
dart run tool/scrape_portal.dart -f json

# Scrape and write output directly to a JSON file
dart run tool/scrape_portal.dart -f json -o tmp/portal_endpoints.json

# Pass username via command line and password via environment variable
NTUT_PORTAL_PASSWORD=<password> dart run tool/scrape_portal.dart -u <username>

The scraper logs in to the NTUT portal using the mobile App User-Agent flow to bypass the web login captcha, fetches the portal tree page (aptreeMain.do), extracts the SSO targets, and deduplicates the results automatically.

Command Options & Usage

Run the tool using Dart:

dart run tool/scrape_portal.dart [arguments]

Available Arguments:

  • -u, --username: NTUT portal username (can also be set via NTUT_PORTAL_USERNAME or NTUT_TEST_USERNAME environment variables).
  • -c, --config: Path to config JSON (defaults to test/test_config.json if credentials aren't passed via CLI or env variables).
  • -f, --format: Output format, either text (default) or json.
  • -o, --output: Path to write the output content. Prints to standard output if omitted.
  • -h, --help: Displays help info.

Password must be supplied either via the config file or via the NTUT_PORTAL_PASSWORD / NTUT_TEST_PASSWORD environment variables to prevent command history leaks.

Local Development

Android SDK: Install Android Studio or let Flutter download SDK components automatically on first build.

VS Code users: See .vscode/README.md for project-specific setup instructions.

Contributors: See CONTRIBUTING.md for commit and branch guidelines.

Project Context

Check AGENTS.md to see detailed architecture notes, implementation status, and future plans.

This project exists alongside two other implementations:

Project Tattoo aims to take lessons learned from both and create a clean, maintainable foundation for future development.

License

Copyright (C) 2026 NTUT Programming Club (NTUT-NPC)

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

See LICENSE for details.

Libraries

components/anchored_popup_menu
components/app_skeleton
components/chip_tab_switcher
components/feature_card
components/floating_action_bar
components/i_school_plus_network_guide
components/notices
components/option_entry_tile
components/section_header
components/widget_preview_frame
database/actions
database/database
database/database.steps
database/schema
Database schema definitions for Tattoo.
database/views
firebase_options
firebase_options_production
firebase_options_staging
i18n/strings.g
Generated file. Do not edit.
i18n/strings_en_US.g
Generated file. Do not edit.
main
models/campus_map
models/campus_wifi
models/classroom
models/contributor
models/course
models/login_exception
models/next_course
models/ranking
models/score
models/user
repositories/auth_repository
repositories/calendar_repository
repositories/campus_map_campus_outlines
repositories/campus_map_first_floor_outlines
repositories/campus_map_repository
repositories/campus_wifi_repository
repositories/course_repository
repositories/language_repository
repositories/portal_repository
repositories/preferences_repository
repositories/student_repository
router/app_router
screens/main/calendar/calendar_providers
screens/main/calendar/calendar_screen
screens/main/course_table/course_materials_list
screens/main/course_table/course_table_cell
screens/main/course_table/course_table_colors
screens/main/course_table/course_table_detail_sheet
screens/main/course_table/course_table_entrance_animation
screens/main/course_table/course_table_export
screens/main/course_table/course_table_grid
screens/main/course_table/course_table_providers
screens/main/course_table/course_table_screen
screens/main/course_table/course_table_weekly
screens/main/course_table_providers
screens/main/home/home_providers
screens/main/home/home_screen
screens/main/home/next_course_card
screens/main/home/student_union_screen
screens/main/home_screen
screens/main/i_school_plus_providers
screens/main/map/campus_map_area_labels
screens/main/map/campus_map_building_labels
screens/main/map/campus_map_campus_layer
screens/main/map/campus_map_campus_selector
screens/main/map/campus_map_floor_selector
screens/main/map/campus_map_geometry
screens/main/map/campus_map_label_area
screens/main/map/campus_map_labels
screens/main/map/campus_map_providers
screens/main/map/campus_map_room_labels
screens/main/map/campus_map_screen
screens/main/map/campus_map_search_dialog
screens/main/map/campus_map_search_results
screens/main/map/campus_map_space_style
screens/main/map/campus_map_tile_provider
screens/main/map/campus_map_viewport
screens/main/map/campus_map_zoom
screens/main/portal/portal_screen
screens/main/profile/about_screen
screens/main/profile/ntut_wifi_android_guide
screens/main/profile/ntut_wifi_ios_guide
screens/main/profile/ntut_wifi_manual_guide
screens/main/profile/ntut_wifi_screen
screens/main/profile/preference_definitions
screens/main/profile/preference_detail_screen
screens/main/profile/preference_providers
screens/main/profile/preferences_screen
screens/main/profile/profile_card
screens/main/profile/profile_danger_zone
screens/main/profile/profile_providers
screens/main/profile/profile_screen
screens/main/profile/regedit_screen
screens/main/scanner/scanner_guide_bottom_sheet
screens/main/scanner/scanner_screen
screens/main/score/score_providers
screens/main/score/score_screen
screens/main/score/score_view_helpers
screens/main/user_providers
screens/update_screen
screens/welcome/change_password_providers
screens/welcome/change_password_screen
screens/welcome/intro_screen
screens/welcome/login_screen
screens/whats_new_screen
services/campus_wifi/android_campus_wifi_provisioner
services/campus_wifi/campus_wifi_platform
services/campus_wifi/campus_wifi_provisioner
services/campus_wifi/ios_campus_wifi_provisioner
services/campus_wifi/ntut8021x_auto_reprovision
services/campus_wifi/ntut8021x_state_store
services/course/course_service
services/course/mock_course_service
services/course/ntut_course_service
services/course_widget_platform
services/course_widget_sync
services/demo_mode
services/file_save_service
services/firebase_service
services/github_service
services/i_school_plus/i_school_plus_service
services/i_school_plus/mock_i_school_plus_service
services/i_school_plus/ntut_i_school_plus_service
services/map/campus_map_parser
services/map/campus_map_service
services/map/mock_campus_map_service
services/map/ntut_campus_map_service
services/portal/mock_portal_service
services/portal/ntut_portal_service
services/portal/portal_service
services/student_query/mock_student_query_service
services/student_query/ntut_student_query_service
services/student_query/student_query_service
services/system_settings
services/update_service
shells/adaptive_navigation_scaffold
shells/animated_shell_container
shells/centered_max_width_frame
shells/showcase_shell
utils/auto_spacing
utils/avatar_payload
utils/course_schedule
utils/http
utils/launch_url
utils/localized
utils/material_file_name
utils/network_error
utils/network_error_io
utils/network_error_stub
utils/pref_type
utils/shared_preferences