diff --git a/.gitignore b/.gitignore index 4dfba9d..a3b0cca 100644 --- a/.gitignore +++ b/.gitignore @@ -149,9 +149,11 @@ coverage/lcov.info **/.idea/uiDesigner.xml # VS Code -**/.vscode/launch.json +# VS Code (keep launch.json tracked, ignore tasks to stay client-side) **/.vscode/extensions.json -**/.vscode/settings.json (optional - remove if you want to track workspace settings) +**/.vscode/settings.json +**/.vscode/tasks.json +tool/bootstrap.sh # Dart Observatory/DevTools **/observatory_tool diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4df3bf9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,52 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Flutter: Android (pick device)", + "request": "launch", + "type": "dart", + "program": "lib/main.dart", + "args": [] + }, + { + "name": "Flutter: iOS Simulator (if configured)", + "request": "launch", + "type": "dart", + "program": "lib/main.dart", + "args": [] + }, + { + "name": "Flutter: Chrome (Web)", + "request": "launch", + "type": "dart", + "program": "lib/main.dart", + "deviceId": "chrome", + "args": [] + }, + { + "name": "Flutter: macOS Desktop", + "request": "launch", + "type": "dart", + "program": "lib/main.dart", + "deviceId": "macos", + "args": [] + } + , + { + "name": "Flutter: Windows Desktop", + "request": "launch", + "type": "dart", + "program": "lib/main.dart", + "deviceId": "windows", + "args": [] + }, + { + "name": "Flutter: Linux Desktop", + "request": "launch", + "type": "dart", + "program": "lib/main.dart", + "deviceId": "linux", + "args": [] + } + ] +} diff --git a/android/app/build.gradle b/android/app/build.gradle index de3c39a..48d283a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -41,7 +41,7 @@ android { defaultConfig { applicationId "com.example.wordlist_elicitation" - minSdk 21 + minSdkVersion flutter.minSdkVersion targetSdk 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/build.gradle b/android/build.gradle index 8164050..62fb248 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.9.0' + ext.kotlin_version = '1.9.24' repositories { google() mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:8.1.0" + classpath "com.android.tools.build:gradle:8.5.2" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c85cfe --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/lib/providers/wordlist_provider.dart b/lib/providers/wordlist_provider.dart index 38e9938..74ffa51 100644 --- a/lib/providers/wordlist_provider.dart +++ b/lib/providers/wordlist_provider.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import '../utils/logger.dart'; import '../models/wordlist_entry.dart'; import '../services/database_service.dart'; @@ -25,8 +26,8 @@ class WordlistProvider extends ChangeNotifier { try { _entries = await _db.getAllWordlistEntries(); _currentIndex = 0; - } catch (e) { - print('Error loading wordlist: $e'); + } catch (e, st) { + Log.e('Error loading wordlist', e, st); } finally { _isLoading = false; notifyListeners(); @@ -41,8 +42,8 @@ class WordlistProvider extends ChangeNotifier { _entries[index] = entry; notifyListeners(); } - } catch (e) { - print('Error updating entry: $e'); + } catch (e, st) { + Log.e('Error updating entry', e, st); } } diff --git a/lib/services/export_service.dart b/lib/services/export_service.dart index dbf9dc2..348464c 100644 --- a/lib/services/export_service.dart +++ b/lib/services/export_service.dart @@ -2,8 +2,6 @@ import 'dart:io'; import 'dart:convert'; import 'package:path_provider/path_provider.dart'; import 'package:archive/archive_io.dart'; -import '../models/wordlist_entry.dart'; -import '../models/consent_record.dart'; import 'xml_service.dart'; import 'database_service.dart'; @@ -26,7 +24,8 @@ class ExportService { final entries = await _db.getAllWordlistEntries(); final xmlContent = await _xmlService.exportDekerekeXml(entries); final xmlFile = File('${exportDir.path}/wordlist_data.xml'); - await xmlFile.writeAsString(xmlContent, encoding: utf16); + // Write XML as UTF-8 (standard, widely compatible). Ensure XML header matches. + await xmlFile.writeAsString(xmlContent, encoding: utf8); // 2. Copy audio files final audioExportDir = Directory('${exportDir.path}/audio'); diff --git a/lib/services/xml_service.dart b/lib/services/xml_service.dart index 8639865..3a28318 100644 --- a/lib/services/xml_service.dart +++ b/lib/services/xml_service.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:xml/xml.dart'; +import '../utils/logger.dart'; import '../models/wordlist_entry.dart'; import 'database_service.dart'; @@ -44,8 +45,8 @@ class XmlImportService { await _db.insertWordlistEntry(entry); importCount++; - } catch (e) { - print('Error parsing entry: $e'); + } catch (e, st) { + Log.w('Error parsing entry', e, st); // Continue with next entry } } @@ -60,8 +61,9 @@ class XmlImportService { /// Export to Dekereke XML format Future exportDekerekeXml(List entries) async { - final builder = XmlBuilder(); - builder.processing('xml', 'version="1.0" encoding="UTF-16"'); + final builder = XmlBuilder(); + // Match the file write encoding (UTF-8) + builder.processing('xml', 'version="1.0" encoding="UTF-8"'); builder.element('Wordlist', nest: () { for (final entry in entries) { diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart new file mode 100644 index 0000000..5f29be5 --- /dev/null +++ b/lib/utils/logger.dart @@ -0,0 +1,30 @@ +import 'package:flutter/foundation.dart'; + +/// Tiny logging helper that is safe for production (no prints) and +/// concise for development. Uses debugPrint to avoid console flooding. +class Log { + static void d(String message) { + if (kDebugMode) debugPrint('[DEBUG] $message'); + } + + static void i(String message) { + debugPrint('[INFO] $message'); + } + + static void w(String message, [Object? error, StackTrace? stackTrace]) { + final suffix = _formatSuffix(error, stackTrace); + debugPrint('[WARN] $message$suffix'); + } + + static void e(String message, [Object? error, StackTrace? stackTrace]) { + final suffix = _formatSuffix(error, stackTrace); + debugPrint('[ERROR] $message$suffix'); + } + + static String _formatSuffix(Object? error, StackTrace? stackTrace) { + final parts = []; + if (error != null) parts.add(error.toString()); + if (stackTrace != null) parts.add('\n$stackTrace'); + return parts.isEmpty ? '' : ' :: ${parts.join(' :: ')}'; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 154e175..772016d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,12 +28,12 @@ dependencies: archive: ^3.4.9 # Audio Recording & Playback - record: ^5.0.4 + record: ^6.1.2 audioplayers: ^5.2.1 permission_handler: ^11.1.0 # UI Components - intl: ^0.18.1 + intl: ^0.20.2 share_plus: ^7.2.1 dev_dependencies: diff --git a/test_data/sample_wordlist.xml b/test_data/sample_wordlist.xml index c9e6f59..b467973 100644 Binary files a/test_data/sample_wordlist.xml and b/test_data/sample_wordlist.xml differ