luqta_sdk 1.6.0
luqta_sdk: ^1.6.0 copied to clipboard
Official Luqta Flutter SDK for contests, quizzes, rewards & gamification. Preconfigured UI or custom API mode. Level/contest availability checks, profile icon, auto token refresh, and full RTL support.
Changelog #
All notable changes to the Luqta Flutter SDK will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.6.0 - 2026-08-31 #
Geolocation / AR levels now behave the way the web build does. Before this release they could not be played at all.
Fixed #
- Geolocation levels were permanently out of range. The SDK had no way to
read the player's position — no geolocation dependency, and
LevelLocationsScreenwas constructed withoutuserLat/userLng— so distance was always null, the geofence check always failed, and every "Hunt Here" tap showed the out-of-range sheet. The SDK now watches position itself (LuqtaLocationService), continuously rather than as a single fix, so a player who walks into range sees the list unlock without reopening the screen. - The directions sheet bypassed the geofence. Its "Hunt Here" button passed a
hardcoded
inRange: true, letting a player start a hunt from any distance. Every route into the AR view now goes through the one gate, which re-resolves the distance in case the player moved while the sheet was open. - AR engine assets were not bundled. Flutter asset directory entries are not
recursive, so
resources/draco/(the mesh decoder),resources/fonts/*andresources/splat/never made it into the build. The local AR server answered 404 and the engine failed silently. All folders are now declared. direction.targetwas ignored. A point withdirection.enabled: truemeasured distance and opened directions against its own coordinates instead of the configured target. The radius still comes from the point itself, matching web.- "Get Directions" opened a search, not a route. Now uses
maps/dir/?api=1&destination=. - Multi-point levels reported complete after one point. Level completion now
follows the server's
is_level_completedrather than assuming the first win closed the level. - A failed save showed a congratulation anyway. A
completeGeoPoint/completeGeoLevelfailure now returns the player to the brief with a message naming the stage that failed, so the win can be retried instead of being lost. find_gifthunts got a timer they were never meant to have.duration_sec: 0is howar_configsays untimed, but the AR scene was handedsecs=120because a zero was coerced to a fallback. Passed through as-is now, matching web.- Local AR asset server could bind twice. Two concurrent
getPort()calls both passed the "already started" check; the second socket leaked and the WebView could be pointed at a dead port. webview_flutter_androidwas imported but not declared as a dependency.formatDistancedid not match web. Now450m,2km,2km 340m(was450 m,2.3 km).ArConfig.toJson()dropped 17 of its 28 fields. Everything specific tofind_giftandcatch_logos— the drift, drop, lane and spin tuning — was parsed but never written back, so a config that round-tripped throughtoJsonsilently reverted to mode defaults.
Added #
- Point map with every geofence drawn, matching the web build's Leaflet map
(OpenStreetMap tiles via
flutter_map). The marker follows the open point, or the player when the list is closed. - "How to Play" interstitial before a player's first hunt, shown once.
- Location status notices — "Finding your location…", and a distinct message plus a Settings or Retry action for denied permission and for location switched off device-wide.
- Geolocation strings ported from the web build's i18n, English and Arabic.
- RTL layout on the hunt screens.
- Missing-permission diagnostics. A host app that never declared the location
permission now gets
LuqtaLocationService.setupHinton the log — naming the exact manifest andInfo.plistentries — instead of the player being shown a prompt loop the OS will never satisfy. - The local AR server logs its 404s, naming the file and both paths it tried. A silent 404 is how the unbundled engine resources went unnoticed in the first place.
Documentation #
- README — a Platform setup section (Android manifest, iOS
Info.plist, what the SDK handles vs what the host must declare, and what not to do), plus Geolocation levels and The AR hunt. - API_REFERENCE — a Geolocation & AR chapter, which the reference previously
had nothing on at all: every screen and service, the full
ArConfigfield reference with per-mode defaults, the three hunt modes, how the bundled 8th Wall engine is served, the JavaScript bridge messages, and AR troubleshooting.
Notes for host apps #
- Do not copy the 8th Wall engine into your app's
assets/. It ships insideluqta_sdkand Flutter bundles package assets automatically; the SDK serves it frompackages/luqta_sdk/assets/. A second copy adds ~23 MB to the APK and is never read. The example app's duplicate has been removed. - Location permissions must be declared by the host app — the geolocation plugin
declares none of its own.
CAMERAneeds nothing: the scanner plugin declares it and it merges automatically. See Platform setup.
Changed #
LevelArScreentakes an optionallocationServiceso a win is recorded at the player's current position rather than the one they arrived with, and pops whether the level closed (the point is reported throughonCompleted).LevelLocationsScreen'suserLat/userLngare now an optional override for hosts that already track the player; omit them and the SDK watches on its own.- New dependencies:
geolocator,flutter_map,latlong2. Host apps running geolocation levels need location permissions declared — see Platform setup.
1.5.3 - 2026-08-17 #
Added #
zeroStorageconfiguration: ConfigurablezeroStorageflag inLuqtaInitConfigto disable local persistent token/cache storage for guest and privacy modes.
Changed #
- Dynamic Theme Synchronization:
LuqtaTheme.generateTheme()automatically detects background darkness (#1A1A2E), configuring card surfaces (#24243D), contrast text, and material dark color schemes.- Action buttons ("Participate", "Continue", "Verify", "Start") and action bar containers dynamically inherit
branding.primaryColorandbranding.secondaryColorgradients (LuqtaTheme.getPrimaryGradient()). - All loading indicators (
CircularProgressIndicator), "View All >" text and arrow icon, carousel indicator dots, and refresh buttons dynamically consumeTheme.of(context).colorScheme.primary.
1.5.2 - 2026-05-13 #
Fixed #
- Contest detail page: empty levels section.
getContestDetailsProgresssometimes returns levels as a sibling of the contest object (data: { contest: {...}, levels: [...] }) instead of nested inside it. The renderer now normalizes all three known response shapes before parsing, so the levels list always renders. - Contest detail page: stuck "Participate" button.
_buildBottomPlayButtononly checkeduser_progress_of_contest.participant_id, missing the case where progress was reported viaparticipate_progress.participant_id. Now usesContest.hasUserParticipatedso both shapes hide the button correctly. - Level parsing:
Map<dynamic, dynamic>rejection. Level entries arriving as rawMap(without the explicit<String, dynamic>type) are now normalized before parsing instead of being silently dropped.
1.5.1 - 2026-05-13 #
Fixed #
- Private contest: re-prompting for access code after entry. Tapping Participate on a private contest the user had already joined would re-open the access code bottom sheet (and the backend would then reject the second join with "already participated"). The check now relies on
hasUserParticipated(which inspects bothuser_progress_of_contest.participant_idandparticipate_progress.participant_id) and reuses the access code already saved when the detail page was opened, so returning users are never re-prompted.
1.5.0 - 2026-04-30 #
Added #
Level.levelTimeline: New field on theLevelmodel — an end-of-level marker returned by some API endpoints in place ofend_date. Supported as either an ISO-8601 string or a numeric epoch timestamp.- Level availability checks:
LevelDateParser.isLevelAvailable(level)evaluatesstartDate,endDate, andlevelTimeline. The pre-built UI now shows localized "not started yet" / "already ended" alerts instead of silently hiding the action button. - Contest availability checks: Contest detail screen shows a localized alert when a contest has not started or has ended, matching the level check pattern.
levels.completeClientWebhook(levelId, variables): Dedicated method forclient_webhooklevel type — POSTs collected form variables to the/sdk/app/client-webhookendpoint.- Auto user-sync on
initializeUserfailure: WhenLuqtaConfig.userProfileis set andinitializeUserreturnsSDK-USER-001("sync user first"), the SDK automatically callssyncAndInitializeUseronce without surfacing the error to the caller. - Silent token refresh with retry: Expired token errors (
ERROR_FATAL+ "token expired") now trigger a transparent re-initialization followed by a single automatic retry of the failed operation. showProfileIcon(LuqtaConfig): New opt-in flag. Whentrueand the user is initialized, a draggable circular button is rendered (bottom-right). Tapping it opens a bottom sheet with the user's name, contact, total points, and contest statistics pulled from/sdk/app/user/profile.- Profile translations: Added
profile.verified,profile.totalPoints,profile.contestsJoined,profile.completed,profile.inProgress, andprofile.levelsCompletedto bothenandarlocales.
Changed #
Level.fromJson/LevelWithProgress.fromJson:_dateToStringhelper now coercesintanddoubleepoch values to their string representation so downstream date parsers always receive a consistent type.- README: Completely rewritten — full configuration table, level-type reference, date handling guide, availability-check examples, updated API reference, and improved code examples throughout.
Fixed #
- Level completion for
luqta_weebhooktype (backend typo) is now correctly handled alongsideluqta_webhook.
1.4.0 - 2026-03-27 #
Added #
- Zero Storage Mode (
zeroStorage: trueinLuqtaConfig): Privacy-first option — no PII is ever written to any database column in plain text. The client sends plain data +zero_storage: true; the server computes all SHA-256 hashes. Onlysdk_hash_useris populated. LuqtaUser.username: New username identifier inLuqtaUserfor SDK-level user identification.LuqtaUser.uuid: New UUID identifier inLuqtaUser(valid UUID v4 format).UserProfile.uuid: Sync a UUID with the user profile.UserProfile.username: Sync a custom username (sdk_username) with the user profile.UserProfile.userId: Sync an external user ID (sdk_user_id) with the user profile.
Changed #
LuqtaUsernow accepts any of:email,phoneNumber,username,uuid— at least one required. Identifier priority for hash lookup:uuid→username→email→phoneNumber.syncUser()identifier validation now accepts uuid, username, or userId in addition to email/phone.initializeUser()includesusername,uuid, andzero_storagein theX-Userheader when set.- Updated README with zero storage section, new identifier examples, and updated user identification table.
Security #
- Zero storage: even if the database is compromised, no user PII can be recovered — only SHA-256 hashes are stored server-side.
- Re-syncing with
zeroStorage: trueautomatically clears all previously stored PII columns.
1.3.3 - 2026-03-09 #
Fixed #
- Level Description: Level list items now show the actual level description instead of static "Task Description" text
- Image Level Crash: Fixed app crash when opening image level completion screen caused by
Color.withValues()API incompatibility with Flutter versions below 3.27. Replaced allwithValues(alpha:)calls withwithOpacity()for broader Flutter compatibility - QR Scanner Crash: Fixed same
withValues()incompatibility in QR scanner widget - Toast Widget: Fixed
withValues()incompatibility in LuqtaToast widget - Publishing: Fixed package publishing to include lib/ source files
1.3.0 - 2026-01-26 #
Added #
- LuqtaToast: New high z-index toast notification widget that appears above bottom sheets
- Access Code Storage: Automatic caching of private contest access codes for seamless re-entry
- Shimmer Loading: Added shimmer effect to contest cards during loading states
- See All Button: New "See All" button for viewing all contests in vertical list
Changed #
- Backend Error Messages: All API errors now show actual backend error messages (e.g., "Level completion criteria not met") instead of generic translations
- Error Handling: Improved
_getErrorMessage()helper extractsLuqtaError.messagewith proper fallbacks - Private Contest Flow: Enhanced flow - checks local storage first, calls API with access code, shows bottom sheet only if needed
Fixed #
- Fixed error messages showing full
LuqtaError.toString()output instead of just the message - Fixed duplicate error toasts in level completion flow
- Fixed access code validation to properly show backend error messages
Improved #
- README: Completely rewritten with comprehensive documentation for both Preconfigured and Custom modes
- Added complete examples, API reference tables, and widget documentation
1.2.0 - 2026-01-26 #
Added #
- Carousel Layout: New horizontal swipe carousel view for contests (matching Figma design)
- Countdown Timer: Live countdown showing time remaining for each contest
- Info Pills: Display awards count, levels count, and timer on contest cards
- Company Logos: Overlapping company logos in top-left corner of cards
- Page Indicator: Animated dots showing current carousel position
- Segmented Progress Bar: Visual progress indicator with checkmarks for completed levels
- Company Info Section: Horizontal scrolling company cards at bottom of detail screen
- See More/Less: Expandable description text on contest detail
- New translations for carousel UI (enter, awards, levels, left, ended, play, seeMore, seeLess, completeAllLevels)
Changed #
- ContestCard: Complete redesign with full-bleed banner images and gradient overlay
- ContestsScreen: Replaced grid/list view with swipeable carousel
- ContestDetailScreen: Complete redesign matching Figma:
- Banner carousel with back/share buttons
- Title section with expandable description
- Progress section with info pills and segmented progress bar
- Level items with icons, status badges, points, and Play buttons
- Company info carousel at bottom
- Full-width Play button in bottom navigation
- Button Style: Rounded pill-style "Enter" and "Play" buttons matching Figma
- Level Items: New card-based design with status indicators (completed, locked, playable)
1.1.0 - 2026-01-26 #
Added #
- New
LuqtaColorsclass with complete brand color palette from Figma - New
LuqtaSpacingclass with standardized spacing values - New
LuqtaRadiusclass with standardized border radius values - New
LuqtaDecorationshelper class for common decorations - Gradient header component for screens
- Card shadow and elevation helpers
Changed #
- UI Redesign: Updated all UI components to match Luqta Brand Playbook (Figma)
- Theme: Completely redesigned theme with new colors (#7C3AED primary purple)
- ContestCard: New card design with gradient banners, pill badges, stats row, and outlined/filled buttons
- ContestsScreen: New gradient header, improved search bar, better loading states
- Typography: Updated text styles with proper weights and sizes
- Buttons: New button styles matching Figma (outlined for participate, filled for actions)
- Colors: Updated color palette (primary: #7C3AED, secondary: #8B5CF6)
- Default border radius increased to 16px
1.0.4 - 2026-01-24 #
Fixed #
- Fixed Contest model date fields to handle both int (timestamp) and string formats from API
1.0.3 - 2026-01-24 #
Fixed #
- Changed
borderRadiustype fromint?todouble?in BrandingConfig to fix type compatibility with Flutter widgets
1.0.2 - 2026-01-24 #
Fixed #
- Added missing
branding.dartimports to all UI widget files and theme.dart - Fixed
BrandingConfigtype resolution in all UI components
1.0.1 - 2026-01-24 #
Fixed #
- Fixed ambiguous
UserProfileexport by removing duplicate class fromconfig.dart - Fixed
PaginatedDataoperator [] errors inluqta_ui_renderer.dartby accessing.itemsproperty directly - Fixed
contests.participate()signature mismatch by using named parameteraccessCode - Fixed
LuqtaTheme.getThemeData()call to use correct method namegenerateTheme()
Changed #
config.dartnow importsUserProfilefromuser_profile.dartandBrandingConfigfrombranding.dart
1.0.0 - 2026-01-24 #
Added - Core SDK #
- Initial release of Luqta Flutter SDK with 95% Web SDK feature parity
LuqtaClientclass with 73 API methods matching Web SDK- Dual-mode support:
LuqtaMode.custom(API-only) andLuqtaMode.preconfigured(auto-rendered UI) - Auto-render functionality with
render()method for instant UI deployment - Dual authentication system: SDK-level and user-level tokens
Added - UI Components #
ContestsScreen- Browse and filter available contestsContestDetailScreen- View contest details, levels, and prizesContestCard- Reusable contest display cardLevelItem- Level progress indicator with statusQuizWidget- Interactive quiz componentCongratulationDialog- Level/contest completion celebration
Added - Level Completion Widgets #
TextLevelWidget- Text answer submissionQRLevelWidget- QR code scanning with cameraImageLevelWidget- Image capture and uploadLinkLevelWidget- URL submission and validation
Added - Services & APIs #
- Contest management: list, details, participate, levels
- Quiz system: questions, answers, scoring
- Rewards: vouchers, coupons, point redemption
- Profile: user data, history, achievements
- Notifications: real-time updates
Added - Theming & Branding #
LuqtaTheme- Complete theming systemBrandingConfig- Customizable colors, fonts, radii- Material Design 3 integration
- Dark mode support
- RTL/Arabic language support
Added - Security #
- Rate limiting protection
- Request deduplication
- Token validation and refresh
- Secure storage for credentials
Platform Support #
- Android
- iOS
- Web
- macOS
- Windows
- Linux