diff --git a/.github/workflows/analyze-test.yaml b/.github/workflows/analyze-test.yaml index c932366b98..c484e65c1c 100644 --- a/.github/workflows/analyze-test.yaml +++ b/.github/workflows/analyze-test.yaml @@ -32,8 +32,8 @@ jobs: - name: Setup flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.22.2" - channel: "stable" + flutter-version: "3.25.0-0.1.pre" + channel: "master" cache: true cache-key: "deps-${{ hashFiles('**/pubspec.lock') }}" cache-path: ${{ runner.tool_cache }}/flutter # optional, change this to specify the cache path diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 06b1f3cf2a..d9e718bb07 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,7 +6,7 @@ on: name: Build dev binaries env: - FLUTTER_VERSION: 3.22.2 + FLUTTER_VERSION: 3.25.0-0.1.pre XCODE_VERSION: ^15.0.1 jobs: @@ -30,7 +30,7 @@ jobs: uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} - channel: "stable" + channel: "master" cache: true cache-key: deps-${{ hashFiles('**/pubspec.lock') }} # optional, change this to force refresh cache cache-path: ${{ runner.tool_cache }}/flutter # optional, change this to specify the cache path diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index dc7913c4b8..0ceb0ed104 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -23,8 +23,8 @@ jobs: - name: Setup flutter uses: subosito/flutter-action@v2 with: - flutter-version: "3.22.2" - channel: "stable" + flutter-version: "3.25.0-0.1.pre" + channel: "master" cache: true cache-key: deps-${{ hashFiles('**/pubspec.lock') }} # optional, change this to force refresh cache cache-path: ${{ runner.tool_cache }}/flutter # optional, change this to specify the cache path diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml new file mode 100644 index 0000000000..2c56592260 --- /dev/null +++ b/.github/workflows/integration-test.yaml @@ -0,0 +1,55 @@ +name: Integration tests + +on: + workflow_dispatch: + pull_request: + paths-ignore: + - "**/*.md" + +env: + JAVA_VERSION: 22 + +jobs: + integration_test: + name: Run integration tests + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: "temurin" + cache: maven + cache-dependency-path: "tmail_integration_test/pom.xml" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build local image + uses: docker/build-push-action@v6 + with: + load: true + tags: tmail-web:integration-test + + - uses: browser-actions/setup-chrome@v1 + with: + chrome-version: stable + install-chromedriver: true + + - uses: browser-actions/setup-firefox@v1 + - uses: browser-actions/setup-geckodriver@latest + + - name: Configure frontend + env: + FRONTEND_CONFIG: ${{ secrets.FRONTEND_CONFIG }} + FRONTEND_CREDS: ${{ secrets.FRONTEND_CREDS }} + run: ../scripts/setup-desktop-integration-tests.sh + working-directory: tmail_integration_test + + - name: Run integration tests + run: ../scripts/run-desktop-integration-tests.sh + working-directory: tmail_integration_test diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1634e23966..5059d816b7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -6,7 +6,7 @@ on: name: Release env: - FLUTTER_VERSION: 3.22.2 + FLUTTER_VERSION: 3.25.0-0.1.pre XCODE_VERSION: ^15.0.1 jobs: @@ -33,7 +33,7 @@ jobs: uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} - channel: "stable" + channel: "master" cache: true cache-key: deps-${{ hashFiles('**/pubspec.lock') }} # optional, change this to force refresh cache cache-path: ${{ runner.tool_cache }}/flutter # optional, change this to specify the cache path diff --git a/.gitignore b/.gitignore index 77a53b29b0..a1dcd66af6 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,6 @@ app.*.symbols *.g.dart messages_*.dart *.mocks.dart + +# end-to-end test +config.properties \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index abc05c7a36..3c352c8bad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG FLUTTER_VERSION=3.22.2 +ARG FLUTTER_VERSION=3.25.0-0.1.pre # Stage 1 - Install dependencies and build the app # This matches the flutter version on our CI/CD pipeline on Github FROM --platform=amd64 ghcr.io/cirruslabs/flutter:${FLUTTER_VERSION} AS build-env diff --git a/android/app/build.gradle b/android/app/build.gradle index a9c72ee42d..b06ba0158b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,10 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" + id "com.google.gms.google-services" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +13,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -23,13 +25,9 @@ if (flutterVersionName == null) { def flutterMinSdkVersion = localProperties.getProperty('flutter.minSdkVersion') if (flutterMinSdkVersion == null) { - flutterMinSdkVersion = '19' + flutterMinSdkVersion = '21' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { @@ -58,8 +56,8 @@ android { compileOptions { coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } // Use key information file @@ -86,7 +84,6 @@ flutter { dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.work:work-runtime-ktx:2.7.0' implementation 'com.android.support:multidex:1.0.3' implementation 'androidx.window:window:1.0.0' diff --git a/android/build.gradle b/android/build.gradle index 16175d50e6..b42eb3a732 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,17 +1,3 @@ -buildscript { - ext.kotlin_version = '1.9.21' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.google.gms:google-services:4.3.10' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.android.tools.build:gradle:7.1.3' - } -} - allprojects { repositories { google() diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bcf06..36987f639f 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,26 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.9.21" apply false + id "com.google.gms.google-services" version "4.3.14" apply false +} + +include ":app" \ No newline at end of file diff --git a/contact/pubspec.lock b/contact/pubspec.lock index ce6c986509..d59e1fd17f 100644 --- a/contact/pubspec.lock +++ b/contact/pubspec.lock @@ -21,18 +21,18 @@ packages: dependency: transitive description: name: archive - sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265 + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.5.1" + version: "3.6.1" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: bidi - sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63" + sha256: "9a712c7ddf708f7c41b1923aa83648a3ed44cfd75b04f72d598c45e5be287f9d" url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.0.12" boolean_selector: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -85,34 +85,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.12" build_runner_core: - dependency: transitive + dependency: "direct overridden" description: name: build_runner_core - sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" url: "https://pub.dev" source: hosted - version: "7.2.7+1" + version: "7.2.9" built_collection: dependency: transitive description: @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.9.2" characters: dependency: transitive description: @@ -165,18 +165,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.10.0" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -196,18 +196,18 @@ packages: dependency: transitive description: name: cross_file - sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.3+8" + version: "0.3.4+2" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" csslib: dependency: transitive description: @@ -244,18 +244,18 @@ packages: dependency: transitive description: name: device_info_plus - sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95" + sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "9.1.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" dio: dependency: "direct main" description: @@ -284,18 +284,18 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.3" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: @@ -324,10 +324,10 @@ packages: dependency: transitive description: name: flex_seed_scheme - sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f" + sha256: "4cee2f1d07259f77e8b36f4ec5f35499d19e74e17c7dce5b819554914082bc01" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" flutter: dependency: "direct main" description: flutter @@ -385,34 +385,34 @@ packages: dependency: transitive description: name: flutter_image_compress_common - sha256: "7cad12802628706655920089cfe9ee1d1098300e7f39a079eb160458bbc47652" + sha256: "7f79bc6c8a363063620b4e372fa86bc691e1cb28e58048cd38e030692fbd99ee" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.5" flutter_image_compress_macos: dependency: transitive description: name: flutter_image_compress_macos - sha256: fea1e3d71150d03373916b832c49b5c2f56c3e7e13da82a929274a2c6f88251e + sha256: "26df6385512e92b3789dc76b613b54b55c457a7f1532e59078b04bf189782d47" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" flutter_image_compress_platform_interface: dependency: transitive description: name: flutter_image_compress_platform_interface - sha256: eb4f055138b29b04498ebcb6d569aaaee34b64d75fb74ea0d40f9790bf47ee9d + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.5" flutter_image_compress_web: dependency: transitive description: name: flutter_image_compress_web - sha256: da41cc3859f19d11c7d10be615f6a9dcf0907e7daffde7442bf4cc2486663660 + sha256: f02fe352b17f82b72f481de45add240db062a2585850bea1667e82cc4cd6c311 url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.4+1" flutter_inappwebview: dependency: transitive description: @@ -555,10 +555,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" get: dependency: transitive description: @@ -579,10 +579,10 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" html: dependency: transitive description: @@ -595,10 +595,10 @@ packages: dependency: transitive description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.2" http_mock_adapter: dependency: "direct main" description: @@ -627,10 +627,10 @@ packages: dependency: transitive description: name: image - sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "4.1.7" + version: "4.2.0" intl: dependency: transitive description: @@ -668,10 +668,10 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: @@ -693,18 +693,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -749,18 +749,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -836,18 +836,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -860,26 +860,26 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" pdf: dependency: transitive description: name: pdf - sha256: "243f05342fc0bdf140eba5b069398985cdbdd3dbb1d776cf43d5ea29cc570ba6" + sha256: "05df53f8791587402493ac97b9869d3824eccbc77d97855f4545cf72df3cae07" url: "https://pub.dev" source: hosted - version: "3.10.8" + version: "3.11.1" pdf_widget_wrapper: dependency: transitive description: @@ -892,58 +892,58 @@ packages: dependency: transitive description: name: petitparser - sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" platform: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.8" pointer_interceptor: dependency: transitive description: name: pointer_interceptor - sha256: bd18321519718678d5fa98ad3a3359cbc7a31f018554eab80b73d08a7f0c165a + sha256: d0a8e660d1204eaec5bd34b34cc92174690e076d2e4f893d9d68c486a13b07c4 url: "https://pub.dev" source: hosted - version: "0.10.1" + version: "0.10.1+1" pointer_interceptor_ios: dependency: transitive description: name: pointer_interceptor_ios - sha256: "4282ebfe21b54e21e26ab982c6086f0a67dc63423026bfba8db39a2e22045f26" + sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917 url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.10.1" pointer_interceptor_platform_interface: dependency: transitive description: name: pointer_interceptor_platform_interface - sha256: "59a446ead3be360bde72c3725f5ecacbba203c8a760e3061024c20f7da53f825" + sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506" url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.10.0+1" pointer_interceptor_web: dependency: transitive description: name: pointer_interceptor_web - sha256: dfd32b9c6e01a18f80535e7791e9d3add009a3395645d6db2f1e22b2642bfab4 + sha256: "7a7087782110f8c1827170660b09f8aa893e0e9a61431dbbe2ac3fc482e8c044" url: "https://pub.dev" source: hosted - version: "0.10.1" + version: "0.10.2+1" pool: dependency: transitive description: @@ -972,18 +972,18 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" qr: dependency: transitive description: name: qr - sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" quiver: dependency: transitive description: @@ -1004,10 +1004,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -1017,10 +1017,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_helper: dependency: transitive description: @@ -1065,10 +1065,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -1081,10 +1081,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" throttling: dependency: transitive description: @@ -1145,82 +1145,82 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.10" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1233,10 +1233,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: @@ -1249,42 +1249,58 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" win32: dependency: transitive description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + url: "https://pub.dev" + source: hosted + version: "5.5.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "1.1.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" xml: dependency: transitive description: name: xml - sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.4.2" + version: "6.5.0" yaml: dependency: transitive description: @@ -1294,5 +1310,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.20.0-7.0.pre.48" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/contact/pubspec.yaml b/contact/pubspec.yaml index b85a3cd0ff..6d5bc592b2 100644 --- a/contact/pubspec.yaml +++ b/contact/pubspec.yaml @@ -43,6 +43,11 @@ dev_dependencies: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec +dependency_overrides: + build_runner: 2.4.12 + build_runner_core: 7.2.9 + json_annotation: 4.8.1 + # The following section is specific to Flutter. flutter: uses-material-design: true diff --git a/core/lib/core.dart b/core/lib/core.dart index f887e6f075..3d5c4046fb 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -86,7 +86,6 @@ export 'presentation/views/quick_search/quick_search_input_form.dart'; export 'presentation/views/toast/toast_position.dart'; export 'presentation/views/toast/tmail_toast.dart'; export 'presentation/views/bottom_popup/full_screen_action_sheet_builder.dart'; -export 'presentation/views/checkbox/labeled_checkbox.dart'; export 'presentation/views/container/tmail_container_widget.dart'; export 'presentation/views/clipper/side_arrow_clipper.dart'; export 'presentation/views/avatar/gradient_circle_avatar_icon.dart'; diff --git a/core/lib/presentation/constants/color_picker_key_values.dart b/core/lib/presentation/constants/color_picker_key_values.dart new file mode 100644 index 0000000000..545fcf006a --- /dev/null +++ b/core/lib/presentation/constants/color_picker_key_values.dart @@ -0,0 +1,10 @@ +import 'dart:ui'; + +class ColorPickerKeyValues { + static const String colorPickerCancelButton = 'tmail_color_picker_cancel_button'; + static const String colorPickerResetToDefaultButton = 'tmail_color_picker_reset_to_default_button'; + static const String colorPickerSetButton = 'tmail_color_picker_set_button'; + static const String colorPickerCopyButton = 'tmail_color_picker_copy_button'; + + static String colorPickerOption(Color color) => 'tmail_color_picker_option_$color'; +} \ No newline at end of file diff --git a/core/lib/presentation/constants/confirmation_dialog_key_values.dart b/core/lib/presentation/constants/confirmation_dialog_key_values.dart new file mode 100644 index 0000000000..77ec4f5ed5 --- /dev/null +++ b/core/lib/presentation/constants/confirmation_dialog_key_values.dart @@ -0,0 +1,7 @@ +class ConfirmationDialogKeyValues { + static const String title = 'tmail_confirmation_dialog_title'; + static const String content = 'tmail_confirmation_dialog_content'; + static const String confirmButton = 'tmail_confirmation_dialog_confirm_button'; + static const String cancelButton = 'tmail_confirmation_dialog_cancel_button'; + static const String closeButton = 'tmail_confirmation_dialog_close_button'; +} \ No newline at end of file diff --git a/core/lib/presentation/constants/search_key_values.dart b/core/lib/presentation/constants/search_key_values.dart new file mode 100644 index 0000000000..bf0d03e965 --- /dev/null +++ b/core/lib/presentation/constants/search_key_values.dart @@ -0,0 +1,10 @@ +class SearchKeyValues { + static const String searchInputField = 'tmail_search_input_field'; + static const String searchUserResult = 'tmail_search_user_result'; + static const String searchEmailResult = 'tmail_search_email_result'; +} + +// When search input field is focused +// And search input field is not empty +// Then user result should be shown with maximum 2 results +// And email result should be shown with maximum 5 results \ No newline at end of file diff --git a/core/lib/presentation/views/button/icon_button_web.dart b/core/lib/presentation/views/button/icon_button_web.dart index 1269ab16e7..6ffc8a1c9e 100644 --- a/core/lib/presentation/views/button/icon_button_web.dart +++ b/core/lib/presentation/views/button/icon_button_web.dart @@ -121,34 +121,38 @@ Widget buildButtonWrapText(String name, { double? minWidth, EdgeInsetsGeometry? padding, FocusNode? focusNode, + String? semanticIdentifier, IconWebCallback? onTap }) { return Container( height: height ?? 40, padding: padding, constraints: BoxConstraints(minWidth: minWidth ?? 0), - child: ElevatedButton( - focusNode: focusNode, - onPressed: () => onTap?.call(), - style: ButtonStyle( - backgroundColor: WidgetStateProperty.resolveWith( - (Set states) => bgColor ?? AppColor.colorTextButton), - shape: WidgetStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(radius ?? 8), - side: BorderSide( - width: borderColor != null ? 1 : 0, - color: borderColor ?? bgColor ?? AppColor.colorTextButton))), - padding: WidgetStateProperty.resolveWith( - (Set states) => const EdgeInsets.symmetric(horizontal: 16)), - elevation: WidgetStateProperty.resolveWith( - (Set states) => 0)), - child: Text(name, - textAlign: TextAlign.center, - style: textStyle ?? - const TextStyle( - fontSize: 17, - fontWeight: FontWeight.w500, - color: Colors.white)), + child: Semantics( + identifier: semanticIdentifier, + child: ElevatedButton( + focusNode: focusNode, + onPressed: () => onTap?.call(), + style: ButtonStyle( + backgroundColor: WidgetStateProperty.resolveWith( + (Set states) => bgColor ?? AppColor.colorTextButton), + shape: WidgetStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radius ?? 8), + side: BorderSide( + width: borderColor != null ? 1 : 0, + color: borderColor ?? bgColor ?? AppColor.colorTextButton))), + padding: WidgetStateProperty.resolveWith( + (Set states) => const EdgeInsets.symmetric(horizontal: 16)), + elevation: WidgetStateProperty.resolveWith( + (Set states) => 0)), + child: Text(name, + textAlign: TextAlign.center, + style: textStyle ?? + const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w500, + color: Colors.white)), + ), ), ); } \ No newline at end of file diff --git a/core/lib/presentation/views/button/tmail_button_widget.dart b/core/lib/presentation/views/button/tmail_button_widget.dart index b6d06720fb..7a80e6b72b 100644 --- a/core/lib/presentation/views/button/tmail_button_widget.dart +++ b/core/lib/presentation/views/button/tmail_button_widget.dart @@ -40,6 +40,7 @@ class TMailButtonWidget extends StatelessWidget { final MainAxisSize mainAxisSize; final bool isLoading; final Color? hoverColor; + final bool expandedText; const TMailButtonWidget({ super.key, @@ -74,6 +75,7 @@ class TMailButtonWidget extends StatelessWidget { this.mainAxisSize = MainAxisSize.max, this.isLoading = false, this.hoverColor, + this.expandedText = false, }); factory TMailButtonWidget.fromIcon({ @@ -149,6 +151,7 @@ class TMailButtonWidget extends StatelessWidget { BoxBorder? border, int? maxLines, Color? hoverColor, + bool expandedText = false, }) { return TMailButtonWidget( key: key, @@ -172,6 +175,7 @@ class TMailButtonWidget extends StatelessWidget { border: border, maxLines: maxLines, hoverColor: hoverColor, + expandedText: expandedText, ); } @@ -244,6 +248,20 @@ class TMailButtonWidget extends StatelessWidget { softWrap: maxLines == 1 ? CommonTextStyle.defaultSoftWrap : null, ), ) + else if (expandedText) + Expanded( + child: Text( + text, + textAlign: textAlign, + style: textStyle ?? const TextStyle( + fontSize: 12, + color: AppColor.colorTextButtonHeaderThread + ), + maxLines: maxLines, + overflow: maxLines == 1 ? CommonTextStyle.defaultTextOverFlow : null, + softWrap: maxLines == 1 ? CommonTextStyle.defaultSoftWrap : null, + ), + ) else Text( text, @@ -288,6 +306,20 @@ class TMailButtonWidget extends StatelessWidget { softWrap: maxLines == 1 ? CommonTextStyle.defaultSoftWrap : null, ), ) + else if (expandedText) + Expanded( + child: Text( + text, + textAlign: textAlign, + style: textStyle ?? const TextStyle( + fontSize: 12, + color: AppColor.colorTextButtonHeaderThread + ), + maxLines: maxLines, + overflow: maxLines == 1 ? CommonTextStyle.defaultTextOverFlow : null, + softWrap: maxLines == 1 ? CommonTextStyle.defaultSoftWrap : null, + ), + ) else Text( text, diff --git a/core/lib/presentation/views/checkbox/labeled_checkbox.dart b/core/lib/presentation/views/checkbox/labeled_checkbox.dart deleted file mode 100644 index b2a511491e..0000000000 --- a/core/lib/presentation/views/checkbox/labeled_checkbox.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; - -class LabeledCheckbox extends StatelessWidget { - const LabeledCheckbox({Key? key, - required this.label, - this.contentPadding, - this.value, - this.onChanged, - this.activeColor, - this.fontSize = 16, - this.gap = 4.0, - this.bold = false, - this.focusNode, - }) : super(key: key); - - final String label; - final EdgeInsets? contentPadding; - final bool? value; - final Function(bool?)? onChanged; - final Color? activeColor; - final double fontSize; - final double gap; - final bool bold; - final FocusNode? focusNode; - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () => onChanged?.call(!(value ?? false)), - child: Padding( - padding: contentPadding ?? const EdgeInsets.all(0), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Checkbox( - value: value, - activeColor: activeColor, - visualDensity: VisualDensity.compact, - focusNode: focusNode, - onChanged: onChanged, - ), - SizedBox( - width: gap, - ), - Flexible( - child: Text( - label, - style: TextStyle( - fontSize: fontSize, - fontWeight: bold ? FontWeight.bold : FontWeight.normal, - color: Colors.black - ), - ), - ), - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/core/lib/presentation/views/container/tmail_container_widget.dart b/core/lib/presentation/views/container/tmail_container_widget.dart index a23bc11361..a71c830970 100644 --- a/core/lib/presentation/views/container/tmail_container_widget.dart +++ b/core/lib/presentation/views/container/tmail_container_widget.dart @@ -49,6 +49,7 @@ class TMailContainerWidget extends StatelessWidget { type: MaterialType.transparency, child: InkWell( onTap: onTapActionCallback, + excludeFromSemantics: true, onTapDown: onTapActionAtPositionCallback != null ? (detail) { if (onTapActionAtPositionCallback != null) { diff --git a/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart b/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart index 4bd8b2e477..ae3e8b75c2 100644 --- a/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart +++ b/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart @@ -1,4 +1,5 @@ +import 'package:core/presentation/constants/color_picker_key_values.dart'; import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/views/button/icon_button_web.dart'; import 'package:flex_color_picker/flex_color_picker.dart'; @@ -82,28 +83,32 @@ class ColorPickerDialogBuilder { ), Padding( padding: const EdgeInsets.symmetric(vertical: 16), - child: ColorCodeField( - color: _currentColor.value, - colorCodeHasColor: true, - shouldUpdate: _shouldUpdate, - onColorChanged: (Color color) { - if (AppColor.listColorsPicker.any((element) => element.value == color.value)) { - _shouldUpdate = true; - _currentColor.value = color; - } else { - _shouldUpdate = false; - _currentColor.value = Colors.black; - _colorCode = color; - } - }, - onEditFocused: (bool editInFocus) { - _shouldUpdate = editInFocus ? true : false; - }, - copyPasteBehavior: const ColorPickerCopyPasteBehavior( - parseShortHexCode: true, - ), - toolIcons: const ColorPickerActionButtons( - dialogActionButtons: true, + child: Semantics( + excludeSemantics: true, + identifier: ColorPickerKeyValues.colorPickerCopyButton, + child: ColorCodeField( + color: _currentColor.value, + colorCodeHasColor: true, + shouldUpdate: _shouldUpdate, + onColorChanged: (Color color) { + if (AppColor.listColorsPicker.any((element) => element.value == color.value)) { + _shouldUpdate = true; + _currentColor.value = color; + } else { + _shouldUpdate = false; + _currentColor.value = Colors.black; + _colorCode = color; + } + }, + onEditFocused: (bool editInFocus) { + _shouldUpdate = editInFocus ? true : false; + }, + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + parseShortHexCode: true, + ), + toolIcons: const ColorPickerActionButtons( + dialogActionButtons: true, + ), ), ), ), @@ -121,7 +126,8 @@ class ColorPickerDialogBuilder { fontSize: 16, fontWeight: FontWeight.normal), bgColor: AppColor.colorShadowComposer, - onTap: () => cancelActionCallback?.call()), + onTap: () => cancelActionCallback?.call(), + semanticIdentifier: ColorPickerKeyValues.colorPickerCancelButton), buildButtonWrapText( textActionResetDefault ?? '', radius: 5, @@ -132,7 +138,8 @@ class ColorPickerDialogBuilder { fontWeight: FontWeight.normal), bgColor: Colors.white, borderColor: Colors.black26, - onTap: () => resetToDefaultActionCallback?.call()), + onTap: () => resetToDefaultActionCallback?.call(), + semanticIdentifier: ColorPickerKeyValues.colorPickerResetToDefaultButton), buildButtonWrapText( textActionSetColor ?? '', radius: 5, @@ -147,7 +154,8 @@ class ColorPickerDialogBuilder { } else { setColorActionCallback?.call(_currentColor.value); } - }) + }, + semanticIdentifier: ColorPickerKeyValues.colorPickerSetButton), ], ), ) @@ -157,21 +165,25 @@ class ColorPickerDialogBuilder { Widget _itemColorWidget(BuildContext context, Color color) { return Material( color: Colors.transparent, - child: InkWell( - onTap: () { - _shouldUpdate = true; - _currentColor.value = color; - }, - child: Container( - decoration: BoxDecoration( - color: color, - border: Border.all( - color: _currentColor.value == color ? Colors.white : Colors.transparent, - width: 8, + child: Semantics( + excludeSemantics: true, + identifier: ColorPickerKeyValues.colorPickerOption(color), + child: InkWell( + onTap: () { + _shouldUpdate = true; + _currentColor.value = color; + }, + child: Container( + decoration: BoxDecoration( + color: color, + border: Border.all( + color: _currentColor.value == color ? Colors.white : Colors.transparent, + width: 8, + ), ), + width: 40, + height: 40, ), - width: 40, - height: 40, ), ), ); diff --git a/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart b/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart index 8cb4d41c75..ea0f42bda5 100644 --- a/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart +++ b/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart @@ -1,5 +1,6 @@ import 'package:core/core.dart'; +import 'package:core/presentation/constants/confirmation_dialog_key_values.dart'; import 'package:core/presentation/views/dialog/confirm_dialog_button.dart'; import 'package:flutter/material.dart'; @@ -184,13 +185,17 @@ class ConfirmDialogBuilder { if (_onCloseButtonAction != null) Align( alignment: AlignmentDirectional.centerEnd, - child: TMailButtonWidget.fromIcon( - icon: _imagePath.icCircleClose, - iconSize: 30, - padding: const EdgeInsets.all(3), - backgroundColor: Colors.transparent, - margin: const EdgeInsetsDirectional.only(top: 16, end: 16), - onTapActionCallback: _onCloseButtonAction + child: Semantics( + label: ConfirmationDialogKeyValues.closeButton, + container: true, + child: TMailButtonWidget.fromIcon( + icon: _imagePath.icCircleClose, + iconSize: 30, + padding: const EdgeInsets.all(3), + backgroundColor: Colors.transparent, + margin: const EdgeInsetsDirectional.only(top: 16, end: 16), + onTapActionCallback: _onCloseButtonAction + ), ) ), if (_iconWidget != null) @@ -203,10 +208,14 @@ class ConfirmDialogBuilder { Padding( padding: _paddingTitle ?? const EdgeInsetsDirectional.only(top: 12, start: 24, end: 24), child: Center( - child: Text( - _title, - textAlign: TextAlign.center, - style: _styleTitle ?? const TextStyle(fontSize: 20.0, color: AppColor.colorActionDeleteConfirmDialog, fontWeight: FontWeight.w500) + child: Semantics( + identifier: ConfirmationDialogKeyValues.title, + container: true, + child: Text( + _title, + textAlign: TextAlign.center, + style: _styleTitle ?? const TextStyle(fontSize: 20.0, color: AppColor.colorActionDeleteConfirmDialog, fontWeight: FontWeight.w500) + ), ) ) ), @@ -214,9 +223,13 @@ class ConfirmDialogBuilder { Padding( padding: _paddingContent ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Center( - child: Text(_content, - textAlign: TextAlign.center, - style: _styleContent ?? const TextStyle(fontSize: 17.0, color: AppColor.colorMessageDialog) + child: Semantics( + identifier: ConfirmationDialogKeyValues.content, + container: true, + child: Text(_content, + textAlign: TextAlign.center, + style: _styleContent ?? const TextStyle(fontSize: 17.0, color: AppColor.colorMessageDialog) + ), ), ), ) @@ -224,11 +237,15 @@ class ConfirmDialogBuilder { Padding( padding: _paddingContent ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Center( - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: _styleContent ?? const TextStyle(fontSize: 17.0, color: AppColor.colorMessageDialog), - children: listTextSpan + child: Semantics( + identifier: ConfirmationDialogKeyValues.content, + container: true, + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: _styleContent ?? const TextStyle(fontSize: 17.0, color: AppColor.colorMessageDialog), + children: listTextSpan + ), ), ), ), @@ -238,26 +255,34 @@ class ConfirmDialogBuilder { if (_cancelText.isNotEmpty) Padding( padding: const EdgeInsetsDirectional.only(top: 8, start: 16, end: 16), - child: ConfirmDialogButton( - label: _cancelText, - backgroundColor: _colorCancelButton, - borderRadius: _radiusButton, - textStyle: _styleTextCancelButton, - padding: _paddingButton, - maxLines: titleActionButtonMaxLines, - onTapAction: _onCancelButtonAction), + child: Semantics( + identifier: ConfirmationDialogKeyValues.cancelButton, + container: true, + child: ConfirmDialogButton( + label: _cancelText, + backgroundColor: _colorCancelButton, + borderRadius: _radiusButton, + textStyle: _styleTextCancelButton, + padding: _paddingButton, + maxLines: titleActionButtonMaxLines, + onTapAction: _onCancelButtonAction), + ), ), if (_confirmText.isNotEmpty) Padding( padding: const EdgeInsetsDirectional.only(top: 8, start: 16, end: 16), - child: ConfirmDialogButton( - label: _confirmText, - backgroundColor: _colorConfirmButton, - borderRadius: _radiusButton, - textStyle: _styleTextConfirmButton, - padding: _paddingButton, - maxLines: titleActionButtonMaxLines, - onTapAction: _onConfirmButtonAction), + child: Semantics( + identifier: ConfirmationDialogKeyValues.confirmButton, + container: true, + child: ConfirmDialogButton( + label: _confirmText, + backgroundColor: _colorConfirmButton, + borderRadius: _radiusButton, + textStyle: _styleTextConfirmButton, + padding: _paddingButton, + maxLines: titleActionButtonMaxLines, + onTapAction: _onConfirmButtonAction), + ), ), const SizedBox(height: 16), ] @@ -267,24 +292,32 @@ class ConfirmDialogBuilder { child: Row( children: [ if (_cancelText.isNotEmpty) - Expanded(child: ConfirmDialogButton( - label: _cancelText, - backgroundColor: _colorCancelButton, - borderRadius: _radiusButton, - textStyle: _styleTextCancelButton, - padding: _paddingButton, - maxLines: titleActionButtonMaxLines, - onTapAction: _onCancelButtonAction)), + Expanded(child: Semantics( + identifier: ConfirmationDialogKeyValues.cancelButton, + container: true, + child: ConfirmDialogButton( + label: _cancelText, + backgroundColor: _colorCancelButton, + borderRadius: _radiusButton, + textStyle: _styleTextCancelButton, + padding: _paddingButton, + maxLines: titleActionButtonMaxLines, + onTapAction: _onCancelButtonAction), + )), if (_confirmText.isNotEmpty && _cancelText.isNotEmpty) const SizedBox(width: 8), if (_confirmText.isNotEmpty) - Expanded(child: ConfirmDialogButton( - label: _confirmText, - backgroundColor: _colorConfirmButton, - borderRadius: _radiusButton, - textStyle: _styleTextConfirmButton, - padding: _paddingButton, - maxLines: titleActionButtonMaxLines, - onTapAction: _onConfirmButtonAction,)) + Expanded(child: Semantics( + identifier: ConfirmationDialogKeyValues.confirmButton, + container: true, + child: ConfirmDialogButton( + label: _confirmText, + backgroundColor: _colorConfirmButton, + borderRadius: _radiusButton, + textStyle: _styleTextConfirmButton, + padding: _paddingButton, + maxLines: titleActionButtonMaxLines, + onTapAction: _onConfirmButtonAction,), + )) ] )) ] diff --git a/core/lib/presentation/views/quick_search/quick_search_input_form.dart b/core/lib/presentation/views/quick_search/quick_search_input_form.dart index 1a159229d4..0bc099f036 100644 --- a/core/lib/presentation/views/quick_search/quick_search_input_form.dart +++ b/core/lib/presentation/views/quick_search/quick_search_input_form.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; +import 'package:core/presentation/constants/search_key_values.dart'; import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/direction_utils.dart'; @@ -850,47 +851,51 @@ class _TypeAheadFieldQuickSearchState extends State extends State<_SuggestionsList> return _contacts! .map((contact) { if (widget.onContactSuggestionSelected != null) { - return InkWell( - child: widget.contactSuggestionBuilder!(context, contact), - onTap: () => widget.onContactSuggestionSelected?.call(contact), + return Semantics( + identifier: SearchKeyValues.searchUserResult, + container: true, + child: InkWell( + child: widget.contactSuggestionBuilder!(context, contact), + onTap: () => widget.onContactSuggestionSelected?.call(contact), + ), ); } else { return widget.contactSuggestionBuilder!(context, contact); diff --git a/core/pubspec.lock b/core/pubspec.lock index b520f72981..497c2d9c8d 100644 --- a/core/pubspec.lock +++ b/core/pubspec.lock @@ -5,34 +5,39 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "73.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.8.0" archive: dependency: transitive description: name: archive - sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265 + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.5.1" + version: "3.6.1" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -53,10 +58,10 @@ packages: dependency: transitive description: name: bidi - sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63" + sha256: "9a712c7ddf708f7c41b1923aa83648a3ed44cfd75b04f72d598c45e5be287f9d" url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.0.12" boolean_selector: dependency: transitive description: @@ -69,10 +74,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -85,10 +90,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.2" build_resolvers: dependency: transitive description: @@ -101,18 +106,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.12" build_runner_core: - dependency: transitive + dependency: "direct overridden" description: name: build_runner_core - sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" url: "https://pub.dev" source: hosted - version: "7.2.7+1" + version: "7.2.9" built_collection: dependency: "direct main" description: @@ -173,10 +178,10 @@ packages: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -189,18 +194,18 @@ packages: dependency: transitive description: name: cross_file - sha256: "2f9d2cbccb76127ba28528cb3ae2c2326a122446a83de5a056aaa3880d3882c5" + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.3+7" + version: "0.3.4+2" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" csslib: dependency: transitive description: @@ -221,10 +226,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.6" dartz: dependency: "direct main" description: @@ -237,18 +242,18 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95" + sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "9.1.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" dio: dependency: "direct main" description: @@ -277,18 +282,18 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.3" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: @@ -317,10 +322,10 @@ packages: dependency: transitive description: name: flex_seed_scheme - sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f" + sha256: "4cee2f1d07259f77e8b36f4ec5f35499d19e74e17c7dce5b819554914082bc01" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" flutter: dependency: "direct main" description: flutter @@ -378,34 +383,34 @@ packages: dependency: transitive description: name: flutter_image_compress_common - sha256: "7cad12802628706655920089cfe9ee1d1098300e7f39a079eb160458bbc47652" + sha256: "7f79bc6c8a363063620b4e372fa86bc691e1cb28e58048cd38e030692fbd99ee" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.5" flutter_image_compress_macos: dependency: transitive description: name: flutter_image_compress_macos - sha256: fea1e3d71150d03373916b832c49b5c2f56c3e7e13da82a929274a2c6f88251e + sha256: "26df6385512e92b3789dc76b613b54b55c457a7f1532e59078b04bf189782d47" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" flutter_image_compress_platform_interface: dependency: transitive description: name: flutter_image_compress_platform_interface - sha256: eb4f055138b29b04498ebcb6d569aaaee34b64d75fb74ea0d40f9790bf47ee9d + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.5" flutter_image_compress_web: dependency: transitive description: name: flutter_image_compress_web - sha256: da41cc3859f19d11c7d10be615f6a9dcf0907e7daffde7442bf4cc2486663660 + sha256: f02fe352b17f82b72f481de45add240db062a2585850bea1667e82cc4cd6c311 url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.4+1" flutter_inappwebview: dependency: "direct main" description: @@ -548,10 +553,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" get: dependency: "direct main" description: @@ -588,10 +593,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -612,10 +617,10 @@ packages: dependency: transitive description: name: image - sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "4.1.7" + version: "4.2.0" intl: dependency: "direct main" description: @@ -661,18 +666,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -705,6 +710,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: transitive description: @@ -717,26 +730,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" mockito: dependency: "direct dev" description: @@ -797,18 +810,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -829,18 +842,18 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" pdf: dependency: transitive description: name: pdf - sha256: "243f05342fc0bdf140eba5b069398985cdbdd3dbb1d776cf43d5ea29cc570ba6" + sha256: "05df53f8791587402493ac97b9869d3824eccbc77d97855f4545cf72df3cae07" url: "https://pub.dev" source: hosted - version: "3.10.8" + version: "3.11.1" pdf_widget_wrapper: dependency: transitive description: @@ -853,18 +866,18 @@ packages: dependency: transitive description: name: petitparser - sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" platform: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.5" plugin_platform_interface: dependency: "direct dev" description: @@ -877,18 +890,18 @@ packages: dependency: "direct main" description: name: pointer_interceptor - sha256: bd18321519718678d5fa98ad3a3359cbc7a31f018554eab80b73d08a7f0c165a + sha256: d0a8e660d1204eaec5bd34b34cc92174690e076d2e4f893d9d68c486a13b07c4 url: "https://pub.dev" source: hosted - version: "0.10.1" + version: "0.10.1+1" pointer_interceptor_ios: dependency: transitive description: name: pointer_interceptor_ios - sha256: "2e73c39452830adc4695757130676a39412a3b7f3c34e3f752791b5384770877" + sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917 url: "https://pub.dev" source: hosted - version: "0.10.0+2" + version: "0.10.1" pointer_interceptor_platform_interface: dependency: transitive description: @@ -901,10 +914,10 @@ packages: dependency: transitive description: name: pointer_interceptor_web - sha256: "9386e064097fd16419e935c23f08f35b58e6aaec155dd39bd6a003b88f9c14b4" + sha256: "7a7087782110f8c1827170660b09f8aa893e0e9a61431dbbe2ac3fc482e8c044" url: "https://pub.dev" source: hosted - version: "0.10.1+2" + version: "0.10.2+1" pool: dependency: transitive description: @@ -941,10 +954,10 @@ packages: dependency: transitive description: name: qr - sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" shelf: dependency: transitive description: @@ -957,10 +970,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -1010,10 +1023,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -1026,10 +1039,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" throttling: dependency: transitive description: @@ -1082,82 +1095,82 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.10" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1170,10 +1183,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: @@ -1186,42 +1199,58 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" win32: - dependency: transitive + dependency: "direct overridden" description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + url: "https://pub.dev" + source: hosted + version: "5.5.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "1.1.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" xml: dependency: transitive description: name: xml - sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.4.2" + version: "6.5.0" yaml: dependency: transitive description: @@ -1231,5 +1260,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.20.0-7.0.pre.48" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/core/pubspec.yaml b/core/pubspec.yaml index b376cd7b9f..f4b0c4652a 100644 --- a/core/pubspec.yaml +++ b/core/pubspec.yaml @@ -51,7 +51,7 @@ dependencies: get: 4.6.6 - device_info_plus: 8.1.0 + device_info_plus: 9.1.0 flutter_inappwebview: 6.0.0 @@ -61,7 +61,7 @@ dependencies: http: ^1.1.0 - pointer_interceptor: 0.10.1 + pointer_interceptor: 0.10.1+1 flutter_keyboard_visibility: 6.0.0 @@ -73,7 +73,7 @@ dependencies: path_provider: 2.1.1 - collection: 1.18.0 + collection: 1.19.0 intl: 0.19.0 @@ -101,10 +101,15 @@ dev_dependencies: build_runner: 2.3.3 - mockito: 5.4.4 + mockito: 5.4.4 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec +dependency_overrides: + build_runner: 2.4.12 + build_runner_core: 7.2.9 + win32: 5.5.4 + flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in @@ -143,10 +148,9 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages - # This section identifies your Flutter project as a module meant for # embedding in a native host app. These identifiers should _not_ ordinarily # be changed after generation - they are used to ensure that the tooling can # maintain consistency when adding or modifying assets and plugins. # They also do not have any bearing on your native host application's - # identifiers, which may be completely independent or the same as these. \ No newline at end of file + # identifiers, which may be completely independent or the same as these. diff --git a/docs/adr/0052-enable-semantics-to-support-integration-test-on-web.md b/docs/adr/0052-enable-semantics-to-support-integration-test-on-web.md new file mode 100644 index 0000000000..427f0d21a5 --- /dev/null +++ b/docs/adr/0052-enable-semantics-to-support-integration-test-on-web.md @@ -0,0 +1,84 @@ +# 52. Enable Semantics to support integration test on Web + +Date: 2024-08-19 + +## Status + +Accepted + +## Context + +All the UI in a Flutter app is rendered as a `Widget Tree`. So the widgets can't access the widget tree elements. +So we have to use [Semantics](https://api.flutter.dev/flutter/widgets/Semantics-class.html) +so that when Flutter renders the `Widgets Tree`, it also maintains a second tree, called `Semantics Tree`, +which helps us to easily access each element, widget of the user interface. + +## Decision + +1. How to enable Semantics: + +- Enable `Semantics` in main function: + +```dart +void main() { + WidgetsFlutterBinding.ensureInitialized(); + SemanticsBinding.instance.ensureSemantics(); + + runApp(const TMailApp()); +} +``` + +- To access any widget element we just need to wrap it in a `Sematics` widget and set a `label` for it + +```dart +Semantics( + label: 'Title-app', + child: Text('Twake Mail'), +); +``` + +2. To ignore `Sematics` for a specific widget, when we have Semantics enabled + +- Way 1: Set the `excludeSemantics: true` property of the `Semantics` widget + +```dart +Semantics( + label: 'Title-app', + excludeSemantics: true, + child: Text('Twake Mail'), +); +``` + +- Way 2: Use `ExcludeSemantics` widget instead of `Semantics` widget + +```dart +ExcludeSemantics( + child: Text('Twake Mail'), +); +``` + +3. For a widget to be treated as a text field in the Semantics Tree + +Set the ` textField: true` property of the `Semantics` widget + +```dart +Semantics( + label: 'Subject-email', + textField: true, + child: TextField(), +); +``` + +## Consequences + +- Enabling Semantics has given us easy access to the widget elements of each screen. Making `Integration test` implementation easy and efficient. + +## Influence + +Some widgets are not working due to Semantics overlap: + +- Workaround: + - Ignore `Sematics` for a specific widget + - Use the correct properties for each widget used (`textField, button, container,...`) + + diff --git a/email_recovery/pubspec.lock b/email_recovery/pubspec.lock index f95a8c7c70..8b730e9bb4 100644 --- a/email_recovery/pubspec.lock +++ b/email_recovery/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -61,34 +61,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.12" build_runner_core: - dependency: transitive + dependency: "direct overridden" description: name: build_runner_core - sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" url: "https://pub.dev" source: hosted - version: "7.2.7+1" + version: "7.2.9" built_collection: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: built_value - sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.8.0" + version: "8.9.2" characters: dependency: transitive description: @@ -133,18 +133,18 @@ packages: dependency: transitive description: name: code_builder - sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.10.0" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" dart_style: dependency: transitive description: @@ -239,10 +239,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -255,10 +255,10 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" http_mock_adapter: dependency: "direct main" description: @@ -304,18 +304,18 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: "direct main" description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: @@ -328,18 +328,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -376,26 +376,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" mockito: dependency: "direct dev" description: @@ -440,10 +440,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" quiver: dependency: transitive description: @@ -464,10 +464,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -477,10 +477,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_helper: dependency: transitive description: @@ -525,10 +525,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -541,10 +541,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" timing: dependency: transitive description: @@ -573,10 +573,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: @@ -585,14 +585,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" yaml: dependency: transitive description: @@ -602,5 +618,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.4.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/email_recovery/pubspec.yaml b/email_recovery/pubspec.yaml index 9ffdcbdd12..a29f7b0678 100644 --- a/email_recovery/pubspec.yaml +++ b/email_recovery/pubspec.yaml @@ -41,6 +41,11 @@ dev_dependencies: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec +dependency_overrides: + build_runner: 2.4.12 + build_runner_core: 7.2.9 + json_annotation: 4.8.1 + # The following section is specific to Flutter. flutter: uses-material-design: true diff --git a/fcm/pubspec.lock b/fcm/pubspec.lock index bf16ad9212..8b730e9bb4 100644 --- a/fcm/pubspec.lock +++ b/fcm/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -61,34 +61,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.12" build_runner_core: - dependency: transitive + dependency: "direct overridden" description: name: build_runner_core - sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" url: "https://pub.dev" source: hosted - version: "7.2.7+1" + version: "7.2.9" built_collection: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.9.2" characters: dependency: transitive description: @@ -133,18 +133,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.10.0" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" dart_style: dependency: transitive description: @@ -239,10 +239,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -255,10 +255,10 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" http_mock_adapter: dependency: "direct main" description: @@ -304,18 +304,18 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: "direct main" description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: @@ -328,18 +328,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -376,26 +376,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" mockito: dependency: "direct dev" description: @@ -440,10 +440,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" quiver: dependency: transitive description: @@ -464,10 +464,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -477,10 +477,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_helper: dependency: transitive description: @@ -525,10 +525,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -541,10 +541,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" timing: dependency: transitive description: @@ -573,10 +573,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: @@ -585,14 +585,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" yaml: dependency: transitive description: @@ -602,5 +618,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.4.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/fcm/pubspec.yaml b/fcm/pubspec.yaml index e418fae836..b812508c4b 100644 --- a/fcm/pubspec.yaml +++ b/fcm/pubspec.yaml @@ -38,5 +38,10 @@ dev_dependencies: mockito: 5.4.4 +dependency_overrides: + build_runner: 2.4.12 + build_runner_core: 7.2.9 + json_annotation: 4.8.1 + flutter: uses-material-design: true diff --git a/forward/pubspec.lock b/forward/pubspec.lock index bf16ad9212..8b730e9bb4 100644 --- a/forward/pubspec.lock +++ b/forward/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -61,34 +61,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.12" build_runner_core: - dependency: transitive + dependency: "direct overridden" description: name: build_runner_core - sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" url: "https://pub.dev" source: hosted - version: "7.2.7+1" + version: "7.2.9" built_collection: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.9.2" characters: dependency: transitive description: @@ -133,18 +133,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.10.0" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" dart_style: dependency: transitive description: @@ -239,10 +239,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -255,10 +255,10 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" http_mock_adapter: dependency: "direct main" description: @@ -304,18 +304,18 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: "direct main" description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: @@ -328,18 +328,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -376,26 +376,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" mockito: dependency: "direct dev" description: @@ -440,10 +440,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" quiver: dependency: transitive description: @@ -464,10 +464,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -477,10 +477,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_helper: dependency: transitive description: @@ -525,10 +525,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -541,10 +541,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" timing: dependency: transitive description: @@ -573,10 +573,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: @@ -585,14 +585,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" yaml: dependency: transitive description: @@ -602,5 +618,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.4.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/forward/pubspec.yaml b/forward/pubspec.yaml index 6af273b33e..cb9ac164d1 100644 --- a/forward/pubspec.yaml +++ b/forward/pubspec.yaml @@ -41,6 +41,11 @@ dev_dependencies: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec +dependency_overrides: + build_runner: 2.4.12 + build_runner_core: 7.2.9 + json_annotation: 4.8.1 + # The following section is specific to Flutter. flutter: uses-material-design: true diff --git a/integration_test/appium_test.dart b/integration_test/appium_test.dart new file mode 100644 index 0000000000..9a6d39a3e8 --- /dev/null +++ b/integration_test/appium_test.dart @@ -0,0 +1,35 @@ +import 'package:appium_flutter_server/appium_flutter_server.dart'; +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:core/utils/platform_info.dart'; +import 'package:flutter/semantics.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; +import 'package:tmail_ui_user/main.dart'; +import 'package:tmail_ui_user/main/bindings/main_bindings.dart'; +import 'package:tmail_ui_user/main/utils/app_utils.dart'; +import 'package:url_strategy/url_strategy.dart'; +import 'package:worker_manager/worker_manager.dart'; + +void main() { + initializeTest( + callback: (WidgetTester tester) async { + WidgetsFlutterBinding.ensureInitialized(); + if (PlatformInfo.isWeb) { + SemanticsBinding.instance.ensureSemantics(); + } + ThemeUtils.setSystemLightUIStyle(); + + await Future.wait([ + MainBindings().dependencies(), + HiveCacheConfig.instance.setUp(), + Executor().warmUp(), + AppUtils.loadEnvFile() + ]); + await HiveCacheConfig.instance.initializeEncryptionKey(); + + setPathUrlStrategy(); + await tester.pumpWidget(const TMailApp()); + }, + ); +} \ No newline at end of file diff --git a/lib/features/base/base_mailbox_controller.dart b/lib/features/base/base_mailbox_controller.dart index 854eca61a7..d6818cb2cf 100644 --- a/lib/features/base/base_mailbox_controller.dart +++ b/lib/features/base/base_mailbox_controller.dart @@ -369,6 +369,7 @@ abstract class BaseMailboxController extends BaseController { (value) => onRenameMailboxAction(presentationMailbox, MailboxName(value)) ) ).build()), + barrierDismissible: false, barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } diff --git a/lib/features/base/key_values/composer_key_values.dart b/lib/features/base/key_values/composer_key_values.dart new file mode 100644 index 0000000000..6e2e02fc60 --- /dev/null +++ b/lib/features/base/key_values/composer_key_values.dart @@ -0,0 +1,42 @@ +class ComposerKeyValues { + static const String openComposerButton = 'tmail_open_composer_button'; + static const String composerView = 'tmail_composer_view'; + static const String composerToField = 'tmail_composer_to_field'; + static const String composerSubjectField = 'tmail_composer_subject_field'; + static const String composerContentField = 'tmail_composer_content_field'; + static const String composerSendButton = 'tmail_composer_send_button'; + // Added on 16/09/2024 + static const String openWebFromFieldButton = 'tmail_composer_open_web_from_field_button'; + static const String openWebCcFieldButton = 'tmail_composer_open_web_cc_field_button'; + static const String openWebBccFieldButton = 'tmail_composer_open_web_bcc_field_button'; + static const String openMobileSecondaryFieldsButton = 'tmail_composer_open_mobile_secondary_fields_button'; // Mobile only has one button to open all above 3 fields + static const String openFromEmailSelector = 'tmail_composer_open_from_email_selector'; + static const String fromEmailSelectorOption = 'tmail_composer_from_email_selector_option'; + static const String composerCcField = 'tmail_composer_cc_field'; + static const String composerBccField = 'tmail_composer_bcc_field'; + static const String openTextFormattingMenuButton = 'tmail_composer_open_text_formatting_menu_button'; + static const String pickAttachmentButton = 'tmail_composer_pick_attachment_button'; + static const String pickInlineImageButton = 'tmail_composer_pick_inline_image_button'; + static const String toggleCodeViewButton = 'tmail_composer_toggle_code_view_button'; + static const String deleteComposedEmailButton = 'tmail_composer_delete_composed_email_button'; + static const String toggleReadRecipientsButton = 'tmail_composer_toggle_read_recipients_button'; + static const String saveAsDraftButton = 'tmail_composer_save_as_draft_button'; + static const String openMobileMoreOptionsButton = 'tmail_composer_open_mobile_more_options_button'; + static const String richtextHeaderStyleButton = 'tmail_composer_richtext_header_style_button'; + static const String richtextFontSizeButton = 'tmail_composer_richtext_font_size_button'; + static const String richtextFontNameButton = 'tmail_composer_richtext_font_name_button'; + static const String richtextTextColorButton = 'tmail_composer_richtext_text_color_button'; + static const String richtextBackgroundColorButton = 'tmail_composer_richtext_background_color_button'; + static const String richtextBoldToggle = 'tmail_composer_richtext_bold_toggle'; + static const String richtextItalicToggle = 'tmail_composer_richtext_italic_toggle'; + static const String richtextUnderlineToggle = 'tmail_composer_richtext_underline_toggle'; + static const String richtextStrikethroughToggle = 'tmail_composer_richtext_strikethrough_toggle'; + static const String richtextAlignParagraphButton = 'tmail_composer_richtext_align_paragraph_button'; + static const String richtextListStyleButton = 'tmail_composer_richtext_list_style_button'; + // Added on 17/09/2024 + static const String richtextHeaderStyleOption = 'tmail_composer_richtext_header_style_option'; + static const String richtextFontSizeOption = 'tmail_composer_richtext_font_size_option'; + static const String richtextFontNameOption = 'tmail_composer_richtext_font_name_option'; + static const String richtextAlignParagraphOption = 'tmail_composer_richtext_align_paragraph_option'; + static const String richtextListStyleOption = 'tmail_composer_richtext_list_style_option'; +} \ No newline at end of file diff --git a/lib/features/base/key_values/email_details_key_values.dart b/lib/features/base/key_values/email_details_key_values.dart new file mode 100644 index 0000000000..3b84b858b4 --- /dev/null +++ b/lib/features/base/key_values/email_details_key_values.dart @@ -0,0 +1,8 @@ +class EmailDetailsKeyValues { + static const String emailDetails = 'tmail_email_details'; + static const String subject = 'tmail_email_details_subject'; + static const String sender = 'tmail_email_details_sender'; + static const String senderEmail = 'tmail_email_details_sender_email'; + static const String recipient = 'tmail_email_details_recipient'; + // find email content with html "iframe" +} \ No newline at end of file diff --git a/lib/features/base/key_values/email_tile_key_values.dart b/lib/features/base/key_values/email_tile_key_values.dart new file mode 100644 index 0000000000..cb2e9b2576 --- /dev/null +++ b/lib/features/base/key_values/email_tile_key_values.dart @@ -0,0 +1,6 @@ +class EmailTileKeyValues { + static const String emailTile = 'tmail_email_tile'; + static const String sender = 'tmail_email_tile_sender'; + static const String subject = 'tmail_email_tile_subject'; + static const String preview = 'tmail_email_tile_preview'; +} \ No newline at end of file diff --git a/lib/features/base/key_values/login_basic_auth_key_values.dart b/lib/features/base/key_values/login_basic_auth_key_values.dart new file mode 100644 index 0000000000..f1beba593a --- /dev/null +++ b/lib/features/base/key_values/login_basic_auth_key_values.dart @@ -0,0 +1,6 @@ +class LoginBasicAuthKeyValues { + static const String loginView = 'tmail_login_view'; + static const String loginEmailField = 'tmail_login_email_field'; + static const String loginPasswordField = 'tmail_login_password_field'; + static const String loginSignInButton = 'tmail_login_sign_in_button'; +} \ No newline at end of file diff --git a/lib/features/base/key_values/login_key_values.dart b/lib/features/base/key_values/login_key_values.dart new file mode 100644 index 0000000000..512c6ad23e --- /dev/null +++ b/lib/features/base/key_values/login_key_values.dart @@ -0,0 +1,7 @@ +class LoginKeyValues { + static const String loginView = 'tmail_login_view'; + static const String loginEmailField = 'tmail_login_email_field'; + static const String loginHostUrlField = 'tmail_login_host_url_field'; + static const String loginNextButton = 'tmail_login_next_button'; + static const String loginVersionText = 'tmail_login_version_text'; +} \ No newline at end of file diff --git a/lib/features/base/widget/application_version_widget.dart b/lib/features/base/widget/application_version_widget.dart index 3f2f958344..be87d8b162 100644 --- a/lib/features/base/widget/application_version_widget.dart +++ b/lib/features/base/widget/application_version_widget.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/utils/application_manager.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/key_values/login_key_values.dart'; class ApplicationVersionWidget extends StatefulWidget { @@ -37,15 +38,19 @@ class _ApplicationVersionWidgetState extends State { future: _versionStream, builder: (context, snapshot) { if (snapshot.hasData) { - return Padding( - padding: widget.padding ?? const EdgeInsets.only(top: 8), - child: Text( - '${widget.title ?? 'v.'}${snapshot.data}', - textAlign: TextAlign.center, - style: widget.textStyle ?? Theme.of(context).textTheme.labelMedium?.copyWith( - fontSize: 13, - color: AppColor.colorContentEmail, - fontWeight: FontWeight.w500 + return Semantics( + identifier: LoginKeyValues.loginVersionText, + container: true, + child: Padding( + padding: widget.padding ?? const EdgeInsets.only(top: 8), + child: Text( + '${widget.title ?? 'v.'}${snapshot.data}', + textAlign: TextAlign.center, + style: widget.textStyle ?? Theme.of(context).textTheme.labelMedium?.copyWith( + fontSize: 13, + color: AppColor.colorContentEmail, + fontWeight: FontWeight.w500 + ), ), ), ); diff --git a/lib/features/base/widget/drop_down_button_widget.dart b/lib/features/base/widget/drop_down_button_widget.dart index 57e9811c90..b6c1a7b2f7 100644 --- a/lib/features/base/widget/drop_down_button_widget.dart +++ b/lib/features/base/widget/drop_down_button_widget.dart @@ -33,6 +33,7 @@ class DropDownButtonWidget extends StatelessWidget { final double? dropdownWidth; final double? dropdownMaxHeight; final String? hintText; + final String? semanticIdentifier; const DropDownButtonWidget({ Key? key, @@ -52,6 +53,7 @@ class DropDownButtonWidget extends StatelessWidget { this.colorButton = Colors.white, this.tooltip = '', this.hintText, + this.semanticIdentifier, }) : super(key: key); @override @@ -78,26 +80,30 @@ class DropDownButtonWidget extends StatelessWidget { items: items .map((item) => DropdownMenuItem( value: item, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: heightItem, - child: Row(children: [ - Expanded(child: Text(_getTextItemDropdown(context, item: item), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black), - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - )), - if (supportSelectionIcon && item == itemSelected) - SvgPicture.asset(imagePaths.icChecked, - width: sizeIconChecked, - height: sizeIconChecked, - fit: BoxFit.fill) - ]), + child: Semantics( + excludeSemantics: true, + identifier: semanticIdentifier, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: heightItem, + child: Row(children: [ + Expanded(child: Text(_getTextItemDropdown(context, item: item), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black), + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + )), + if (supportSelectionIcon && item == itemSelected) + SvgPicture.asset(imagePaths.icChecked, + width: sizeIconChecked, + height: sizeIconChecked, + fit: BoxFit.fill) + ]), + ), ), ), )) diff --git a/lib/features/base/widget/popup_item_no_icon_widget.dart b/lib/features/base/widget/popup_item_no_icon_widget.dart index 4fbea9c64f..03b91116e4 100644 --- a/lib/features/base/widget/popup_item_no_icon_widget.dart +++ b/lib/features/base/widget/popup_item_no_icon_widget.dart @@ -28,36 +28,39 @@ class PopupItemNoIconWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return PointerInterceptor( - child: InkWell( - onTap: onCallbackAction, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - child: SizedBox( - width: maxWidth, - child: Row(children: [ - Expanded(child: Text( - _nameAction, - style: const TextStyle( - fontSize: 17, - color: Colors.black, - fontWeight: FontWeight.normal - ) - )), - if (isSelected && svgIconSelected != null) - ...[ - const SizedBox(width: 12), - SvgPicture.asset( - svgIconSelected!, - width: 24, - height: 24, - fit: BoxFit.fill - ), - ] - ]), - ), + return Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: InkWell( + onTap: onCallbackAction, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: SizedBox( + width: maxWidth, + child: Row(children: [ + Expanded(child: Text( + _nameAction, + style: const TextStyle( + fontSize: 17, + color: Colors.black, + fontWeight: FontWeight.normal + ) + )), + if (isSelected && svgIconSelected != null) + ...[ + const SizedBox(width: 12), + SvgPicture.asset( + svgIconSelected!, + width: 24, + height: 24, + fit: BoxFit.fill + ), + ] + ]), + ), + ) ) - ) + ), ); } } \ No newline at end of file diff --git a/lib/features/base/widget/popup_item_widget.dart b/lib/features/base/widget/popup_item_widget.dart index 169f51205f..03a14ebea6 100644 --- a/lib/features/base/widget/popup_item_widget.dart +++ b/lib/features/base/widget/popup_item_widget.dart @@ -33,38 +33,41 @@ class PopupItemWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return PointerInterceptor( - child: InkWell( - onTap: onCallbackAction, - child: Container( - height: PopupItemWidgetStyle.height, - constraints: const BoxConstraints(minWidth: PopupItemWidgetStyle.minWidth), - padding: padding, - child: Row(children: [ - SvgPicture.asset( - _iconAction, - width: iconSize ?? PopupItemWidgetStyle.iconSize, - height: iconSize ?? PopupItemWidgetStyle.iconSize, - fit: BoxFit.fill, - colorFilter: colorIcon?.asFilter() - ), - const SizedBox(width: PopupItemWidgetStyle.space), - Expanded(child: Text( - _nameAction, - style: styleName ?? PopupItemWidgetStyle.labelTextStyle - )), - if (isSelected == true && selectedIcon != null) - Padding( - padding: PopupItemWidgetStyle.iconSelectedPadding, - child: SvgPicture.asset( - selectedIcon!, - width: PopupItemWidgetStyle.selectedIconSize, - height: PopupItemWidgetStyle.selectedIconSize, - fit: BoxFit.fill - ), - ) - ]), - ) + return Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: InkWell( + onTap: onCallbackAction, + child: Container( + height: PopupItemWidgetStyle.height, + constraints: const BoxConstraints(minWidth: PopupItemWidgetStyle.minWidth), + padding: padding, + child: Row(children: [ + SvgPicture.asset( + _iconAction, + width: iconSize ?? PopupItemWidgetStyle.iconSize, + height: iconSize ?? PopupItemWidgetStyle.iconSize, + fit: BoxFit.fill, + colorFilter: colorIcon?.asFilter() + ), + const SizedBox(width: PopupItemWidgetStyle.space), + Expanded(child: Text( + _nameAction, + style: styleName ?? PopupItemWidgetStyle.labelTextStyle + )), + if (isSelected == true && selectedIcon != null) + Padding( + padding: PopupItemWidgetStyle.iconSelectedPadding, + child: SvgPicture.asset( + selectedIcon!, + width: PopupItemWidgetStyle.selectedIconSize, + height: PopupItemWidgetStyle.selectedIconSize, + fit: BoxFit.fill + ), + ) + ]), + ) + ), ), ); } diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index a62ebfa680..61894f4852 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -199,6 +199,7 @@ class ComposerController extends BaseController with DragDropFileMixin implement ButtonState _saveToDraftButtonState = ButtonState.enabled; ButtonState _sendButtonState = ButtonState.enabled; SignatureStatus _identityContentOnOpenPolicy = SignatureStatus.editedAvailable; + bool _isEditorClicked = false; late Worker uploadInlineImageWorker; late Worker dashboardViewStateWorker; @@ -261,6 +262,7 @@ class ComposerController extends BaseController with DragDropFileMixin implement subjectEmailInputFocusNode?.removeListener(_subjectEmailInputFocusListener); _composerCacheListener?.cancel(); _beforeReconnectManager.removeListener(onBeforeReconnect); + _isEditorClicked = false; if (PlatformInfo.isWeb) { richTextWebController = null; } else { @@ -1950,7 +1952,15 @@ class ComposerController extends BaseController with DragDropFileMixin implement richTextWebController?.closeAllMenuPopup(); } + void handleOnUnFocusEditorWeb() { + if (_isEditorClicked) { + _isEditorClicked = false; + richTextWebController?.editorController.setFocus(); + } + } + void handleOnMouseDownHtmlEditorWeb(BuildContext context) { + _isEditorClicked = true; Navigator.maybePop(context); FocusScope.of(context).unfocus(); _collapseAllRecipient(); diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 47c42d6123..13f7113825 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:model/email/prefix_email_address.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/base/widget/popup_item_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart'; @@ -48,6 +49,7 @@ class ComposerView extends GetWidget { children: [ if (controller.responsiveUtils.isLandscapeMobile(context)) Obx(() => LandscapeAppBarComposerWidget( + key: const ValueKey(ComposerKeyValues.composerSendButton), isSendButtonEnabled: controller.isEnableEmailSendButton.value, onCloseViewAction: () => controller.handleClickCloseComposer(context), sendMessageAction: () => controller.handleClickSendButton(context), @@ -73,6 +75,7 @@ class ComposerView extends GetWidget { )) else Obx(() => AppBarComposerWidget( + key: const ValueKey(ComposerKeyValues.composerSendButton), isSendButtonEnabled: controller.isEnableEmailSendButton.value, onCloseViewAction: () => controller.handleClickCloseComposer(context), sendMessageAction: () => controller.handleClickSendButton(context), @@ -120,6 +123,7 @@ class ComposerView extends GetWidget { } }), Obx(() => RecipientComposerWidget( + key: const ValueKey(ComposerKeyValues.composerToField), prefix: PrefixEmailAddress.to, listEmailAddress: controller.listToEmailAddress, imagePaths: controller.imagePaths, @@ -196,6 +200,7 @@ class ComposerView extends GetWidget { } }), SubjectComposerWidget( + key: const ValueKey(ComposerKeyValues.composerSubjectField), focusNode: controller.subjectEmailInputFocusNode, textController: controller.subjectEmailInputController, onTextChange: controller.setSubjectEmail, @@ -222,6 +227,7 @@ class ComposerView extends GetWidget { Obx(() => Padding( padding: ComposerStyle.mobileEditorPadding, child: MobileEditorView( + key: const ValueKey(ComposerKeyValues.composerContentField), arguments: controller.composerArguments.value, contentViewState: controller.emailContentsViewState.value, onCreatedEditorAction: controller.onCreatedMobileEditorAction, @@ -276,6 +282,7 @@ class ComposerView extends GetWidget { onChangeIdentity: controller.onChangeIdentity, ), RecipientComposerWidget( + key: const ValueKey(ComposerKeyValues.composerToField), prefix: PrefixEmailAddress.to, listEmailAddress: controller.listToEmailAddress, imagePaths: controller.imagePaths, @@ -344,6 +351,7 @@ class ComposerView extends GetWidget { ], )), SubjectComposerWidget( + key: const ValueKey(ComposerKeyValues.composerSubjectField), focusNode: controller.subjectEmailInputFocusNode, textController: controller.subjectEmailInputController, onTextChange: controller.setSubjectEmail, @@ -370,6 +378,7 @@ class ComposerView extends GetWidget { Obx(() => Padding( padding: ComposerStyle.mobileEditorPadding, child: MobileEditorView( + key: const ValueKey(ComposerKeyValues.composerContentField), arguments: controller.composerArguments.value, contentViewState: controller.emailContentsViewState.value, onCreatedEditorAction: controller.onCreatedMobileEditorAction, @@ -382,6 +391,7 @@ class ComposerView extends GetWidget { ) ), Obx(() => TabletBottomBarComposerWidget( + key: const ValueKey(ComposerKeyValues.composerSendButton), hasReadReceipt: controller.hasRequestReadReceipt.value, deleteComposerAction: () => controller.handleClickDeleteComposer(context), saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context), @@ -424,46 +434,61 @@ class ComposerView extends GetWidget { List _createMoreOptionPopupItems(BuildContext context) { return [ PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, - child: PopupItemWidget( - controller.imagePaths.icReadReceipt, - AppLocalizations.of(context).requestReadReceipt, - styleName: ComposerStyle.popupItemTextStyle, - padding: ComposerStyle.popupItemPadding, - colorIcon: ComposerStyle.popupItemIconColor, - selectedIcon: controller.imagePaths.icFilterSelected, - isSelected: controller.hasRequestReadReceipt.value, - onCallbackAction: () { - popBack(); - controller.toggleRequestReadReceipt(context); - } + child: Semantics( + container: true, + identifier: ComposerKeyValues.toggleReadRecipientsButton, + child: PopupItemWidget( + controller.imagePaths.icReadReceipt, + AppLocalizations.of(context).requestReadReceipt, + styleName: ComposerStyle.popupItemTextStyle, + padding: ComposerStyle.popupItemPadding, + colorIcon: ComposerStyle.popupItemIconColor, + selectedIcon: controller.imagePaths.icFilterSelected, + isSelected: controller.hasRequestReadReceipt.value, + onCallbackAction: () { + popBack(); + controller.toggleRequestReadReceipt(context); + } + ), ) ), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, - child: PopupItemWidget( - controller.imagePaths.icSaveToDraft, - AppLocalizations.of(context).saveAsDraft, - colorIcon: ComposerStyle.popupItemIconColor, - styleName: ComposerStyle.popupItemTextStyle, - padding: ComposerStyle.popupItemPadding, - onCallbackAction: () { - popBack(); - controller.handleClickSaveAsDraftsButton(context); - } + child: Semantics( + container: true, + identifier: ComposerKeyValues.saveAsDraftButton, + child: PopupItemWidget( + controller.imagePaths.icSaveToDraft, + AppLocalizations.of(context).saveAsDraft, + colorIcon: ComposerStyle.popupItemIconColor, + styleName: ComposerStyle.popupItemTextStyle, + padding: ComposerStyle.popupItemPadding, + onCallbackAction: () { + popBack(); + controller.handleClickSaveAsDraftsButton(context); + } + ), ) ), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, - child: PopupItemWidget( - controller.imagePaths.icDeleteMailbox, - AppLocalizations.of(context).delete, - styleName: ComposerStyle.popupItemTextStyle, - padding: ComposerStyle.popupItemPadding, - onCallbackAction: () { - popBack(); - controller.handleClickDeleteComposer(context); - }, + child: Semantics( + container: true, + identifier: ComposerKeyValues.deleteComposedEmailButton, + child: PopupItemWidget( + controller.imagePaths.icDeleteMailbox, + AppLocalizations.of(context).delete, + styleName: ComposerStyle.popupItemTextStyle, + padding: ComposerStyle.popupItemPadding, + onCallbackAction: () { + popBack(); + controller.handleClickDeleteComposer(context); + }, + ), ) ), ]; diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index 6621e9f5f3..5344fb558a 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:model/email/prefix_email_address.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/base/widget/popup_item_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart'; @@ -39,26 +40,277 @@ class ComposerView extends GetWidget { childBuilder: (context, constraints) { return GestureDetector( onTap: () => controller.clearFocus(context), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Obx(() => MobileResponsiveAppBarComposerWidget( - isCodeViewEnabled: controller.richTextWebController!.codeViewEnabled, - isFormattingOptionsEnabled: controller.richTextWebController!.isFormattingOptionsEnabled, - openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions, - isSendButtonEnabled: controller.isEnableEmailSendButton.value, + excludeFromSemantics: true, + child: Semantics( + identifier: ComposerKeyValues.composerView, + container: true, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx(() => MobileResponsiveAppBarComposerWidget( + isCodeViewEnabled: controller.richTextWebController!.codeViewEnabled, + isFormattingOptionsEnabled: controller.richTextWebController!.isFormattingOptionsEnabled, + openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions, + isSendButtonEnabled: controller.isEnableEmailSendButton.value, + onCloseViewAction: () => controller.handleClickCloseComposer(context), + attachFileAction: () => controller.openFilePickerByType(context, FileType.any), + insertImageAction: () => controller.insertImage(context, constraints.maxWidth), + sendMessageAction: () => controller.handleClickSendButton(context), + openContextMenuAction: (position) { + controller.openPopupMenuAction( + context, + position, + _createMoreOptionPopupItems(context), + radius: ComposerStyle.popupMenuRadius + ); + }, + )), + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: ComposerStyle.getMaxHeightEmailAddressWidget( + context, + constraints, + controller.responsiveUtils + ) + ), + child: SingleChildScrollView( + controller: controller.scrollControllerEmailAddress, + child: Obx(() => Column( + children: [ + if (controller.fromRecipientState.value == PrefixRecipientState.enabled) + Tooltip( + message: controller.identitySelected.value?.email ?? '', + child: FromComposerMobileWidget( + selectedIdentity: controller.identitySelected.value, + imagePaths: controller.imagePaths, + responsiveUtils: controller.responsiveUtils, + margin: ComposerStyle.mobileRecipientMargin, + padding: ComposerStyle.mobileRecipientPadding, + onTap: () => controller.openSelectIdentityBottomSheet(context) + ), + ), + RecipientComposerWidget( + prefix: PrefixEmailAddress.to, + listEmailAddress: controller.listToEmailAddress, + imagePaths: controller.imagePaths, + maxWidth: constraints.maxWidth, + fromState: controller.fromRecipientState.value, + ccState: controller.ccRecipientState.value, + bccState: controller.bccRecipientState.value, + expandMode: controller.toAddressExpandMode.value, + controller: controller.toEmailAddressController, + focusNode: controller.toAddressFocusNode, + focusNodeKeyboard: controller.toAddressFocusNodeKeyboard, + keyTagEditor: controller.keyToEmailTagEditor, + isInitial: controller.isInitialRecipient.value, + padding: ComposerStyle.mobileRecipientPadding, + margin: ComposerStyle.mobileRecipientMargin, + nextFocusNode: controller.getNextFocusOfToEmailAddress(), + onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, + onShowFullListEmailAddressAction: controller.showFullEmailAddress, + onAddEmailAddressTypeAction: controller.addEmailAddressType, + onUpdateListEmailAddressAction: controller.updateListEmailAddress, + onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, + onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, + ), + if (controller.ccRecipientState.value == PrefixRecipientState.enabled) + RecipientComposerWidget( + prefix: PrefixEmailAddress.cc, + listEmailAddress: controller.listCcEmailAddress, + imagePaths: controller.imagePaths, + maxWidth: constraints.maxWidth, + expandMode: controller.ccAddressExpandMode.value, + controller: controller.ccEmailAddressController, + focusNode: controller.ccAddressFocusNode, + focusNodeKeyboard: controller.ccAddressFocusNodeKeyboard, + keyTagEditor: controller.keyCcEmailTagEditor, + isInitial: controller.isInitialRecipient.value, + nextFocusNode: controller.getNextFocusOfCcEmailAddress(), + padding: ComposerStyle.mobileRecipientPadding, + margin: ComposerStyle.mobileRecipientMargin, + onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, + onShowFullListEmailAddressAction: controller.showFullEmailAddress, + onDeleteEmailAddressTypeAction: controller.deleteEmailAddressType, + onUpdateListEmailAddressAction: controller.updateListEmailAddress, + onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, + onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, + ), + if (controller.bccRecipientState.value == PrefixRecipientState.enabled) + RecipientComposerWidget( + prefix: PrefixEmailAddress.bcc, + listEmailAddress: controller.listBccEmailAddress, + imagePaths: controller.imagePaths, + maxWidth: constraints.maxWidth, + expandMode: controller.bccAddressExpandMode.value, + controller: controller.bccEmailAddressController, + focusNode: controller.bccAddressFocusNode, + focusNodeKeyboard: controller.bccAddressFocusNodeKeyboard, + keyTagEditor: controller.keyBccEmailTagEditor, + isInitial: controller.isInitialRecipient.value, + nextFocusNode: controller.subjectEmailInputFocusNode, + padding: ComposerStyle.mobileRecipientPadding, + margin: ComposerStyle.mobileRecipientMargin, + onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, + onShowFullListEmailAddressAction: controller.showFullEmailAddress, + onDeleteEmailAddressTypeAction: controller.deleteEmailAddressType, + onUpdateListEmailAddressAction: controller.updateListEmailAddress, + onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, + onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, + ), + ], + )), + ) + ), + SubjectComposerWidget( + focusNode: controller.subjectEmailInputFocusNode, + textController: controller.subjectEmailInputController, + onTextChange: controller.setSubjectEmail, + padding: ComposerStyle.mobileSubjectPadding, + margin: ComposerStyle.mobileSubjectMargin, + ), + Expanded( + child: LayoutBuilder( + builder: (context, constraintsEditor) { + return Stack( + children: [ + Column( + children: [ + Expanded( + child: Padding( + padding: ComposerStyle.mobileEditorPadding, + child: Obx(() => WebEditorView( + editorController: controller.richTextWebController!.editorController, + arguments: controller.composerArguments.value, + contentViewState: controller.emailContentsViewState.value, + currentWebContent: controller.textEditorWeb, + onInitial: controller.handleInitHtmlEditorWeb, + onChangeContent: controller.onChangeTextEditorWeb, + onFocus: controller.handleOnFocusHtmlEditorWeb, + onUnFocus: controller.handleOnUnFocusEditorWeb, + onMouseDown: controller.handleOnMouseDownHtmlEditorWeb, + onEditorSettings: controller.richTextWebController!.onEditorSettingsChange, + onEditorTextSizeChanged: controller.richTextWebController!.onEditorTextSizeChanged, + width: constraints.maxWidth, + height: constraints.maxHeight, + onDragEnter: controller.handleOnDragEnterHtmlEditorWeb, + onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction( + context: context, + maxWidth: constraintsEditor.maxWidth, + listFileUpload: listFileUpload + ), + onPasteImageFailureAction: (listFileUpload, base64, uploadError) => + controller.handleOnPasteImageFailureAction( + context: context, + listFileUpload: listFileUpload, + base64: base64, + uploadError: uploadError + ), + onInitialContentLoadComplete: controller.restoreCollapsibleButton, + )), + ), + ), + Obx(() { + if (controller.uploadController.listUploadAttachments.isNotEmpty) { + return AttachmentComposerWidget( + listFileUploaded: controller.uploadController.listUploadAttachments, + isCollapsed: controller.isAttachmentCollapsed, + onDeleteAttachmentAction: controller.deleteAttachmentUploaded, + onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed, + ); + } else { + return const SizedBox.shrink(); + } + }), + Obx(() { + if (controller.richTextWebController!.isFormattingOptionsEnabled) { + return ToolbarRichTextWebBuilder( + richTextWebController: controller.richTextWebController!, + padding: ComposerStyle.richToolbarPadding, + decoration: const BoxDecoration( + color: ComposerStyle.richToolbarColor, + boxShadow: ComposerStyle.richToolbarShadow + ), + ); + } else { + return const SizedBox.shrink(); + } + }) + ], + ), + Align( + alignment: AlignmentDirectional.topCenter, + child: Obx(() => InsertImageLoadingBarWidget( + uploadInlineViewState: controller.uploadController.uploadInlineViewState.value, + viewState: controller.viewState.value, + padding: ComposerStyle.insertImageLoadingBarPadding, + )), + ), + Obx(() { + if (controller.mailboxDashBoardController.isAttachmentDraggableAppActive) { + return Positioned.fill( + child: PointerInterceptor( + child: AttachmentDropZoneWidget( + imagePaths: controller.imagePaths, + width: constraintsEditor.maxWidth, + height: constraintsEditor.maxHeight, + onAttachmentDropZoneListener: controller.onAttachmentDropZoneListener, + ) + ), + ); + } else { + return const SizedBox.shrink(); + } + }), + Obx(() { + if (controller.mailboxDashBoardController.isLocalFileDraggableAppActive) { + return Positioned.fill( + child: PointerInterceptor( + child: LocalFileDropZoneWidget( + imagePaths: controller.imagePaths, + width: constraintsEditor.maxWidth, + height: constraintsEditor.maxHeight, + onLocalFileDropZoneListener: (details) => + controller.onLocalFileDropZoneListener( + context: context, + details: details, + maxWidth: constraintsEditor.maxWidth, + ), + ) + ), + ); + } else { + return const SizedBox.shrink(); + } + }), + ], + ); + } + ), + ), + ] + ), + ), + ); + } + ), + desktop: Obx(() => DesktopResponsiveContainerView( + childBuilder: (context, constraints) { + return GestureDetector( + onTap: () => controller.clearFocus(context), + excludeFromSemantics: true, + child: Semantics( + identifier: ComposerKeyValues.composerView, + container: true, + child: Column(children: [ + Obx(() => DesktopAppBarComposerWidget( + emailSubject: controller.subjectEmail.value ?? '', + displayMode: controller.screenDisplayMode.value, onCloseViewAction: () => controller.handleClickCloseComposer(context), - attachFileAction: () => controller.openFilePickerByType(context, FileType.any), - insertImageAction: () => controller.insertImage(context, constraints.maxWidth), - sendMessageAction: () => controller.handleClickSendButton(context), - openContextMenuAction: (position) { - controller.openPopupMenuAction( - context, - position, - _createMoreOptionPopupItems(context), - radius: ComposerStyle.popupMenuRadius - ); - }, + onChangeDisplayModeAction: controller.displayScreenTypeComposerAction, + constraints: constraints, )), ConstrainedBox( constraints: BoxConstraints( @@ -73,16 +325,14 @@ class ComposerView extends GetWidget { child: Obx(() => Column( children: [ if (controller.fromRecipientState.value == PrefixRecipientState.enabled) - Tooltip( - message: controller.identitySelected.value?.email ?? '', - child: FromComposerMobileWidget( - selectedIdentity: controller.identitySelected.value, - imagePaths: controller.imagePaths, - responsiveUtils: controller.responsiveUtils, - margin: ComposerStyle.mobileRecipientMargin, - padding: ComposerStyle.mobileRecipientPadding, - onTap: () => controller.openSelectIdentityBottomSheet(context) - ), + FromComposerDropDownWidget( + items: controller.listFromIdentities, + itemSelected: controller.identitySelected.value, + dropdownKey: controller.identityDropdownKey, + imagePaths: controller.imagePaths, + padding: ComposerStyle.desktopRecipientPadding, + margin: ComposerStyle.desktopRecipientMargin, + onChangeIdentity: controller.onChangeIdentity, ), RecipientComposerWidget( prefix: PrefixEmailAddress.to, @@ -98,8 +348,8 @@ class ComposerView extends GetWidget { focusNodeKeyboard: controller.toAddressFocusNodeKeyboard, keyTagEditor: controller.keyToEmailTagEditor, isInitial: controller.isInitialRecipient.value, - padding: ComposerStyle.mobileRecipientPadding, - margin: ComposerStyle.mobileRecipientMargin, + padding: ComposerStyle.desktopRecipientPadding, + margin: ComposerStyle.desktopRecipientMargin, nextFocusNode: controller.getNextFocusOfToEmailAddress(), onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, onShowFullListEmailAddressAction: controller.showFullEmailAddress, @@ -122,8 +372,8 @@ class ComposerView extends GetWidget { keyTagEditor: controller.keyCcEmailTagEditor, isInitial: controller.isInitialRecipient.value, nextFocusNode: controller.getNextFocusOfCcEmailAddress(), - padding: ComposerStyle.mobileRecipientPadding, - margin: ComposerStyle.mobileRecipientMargin, + padding: ComposerStyle.desktopRecipientPadding, + margin: ComposerStyle.desktopRecipientMargin, onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, onShowFullListEmailAddressAction: controller.showFullEmailAddress, onDeleteEmailAddressTypeAction: controller.deleteEmailAddressType, @@ -145,8 +395,8 @@ class ComposerView extends GetWidget { keyTagEditor: controller.keyBccEmailTagEditor, isInitial: controller.isInitialRecipient.value, nextFocusNode: controller.subjectEmailInputFocusNode, - padding: ComposerStyle.mobileRecipientPadding, - margin: ComposerStyle.mobileRecipientMargin, + padding: ComposerStyle.desktopRecipientPadding, + margin: ComposerStyle.desktopRecipientMargin, onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, onShowFullListEmailAddressAction: controller.showFullEmailAddress, onDeleteEmailAddressTypeAction: controller.deleteEmailAddressType, @@ -163,8 +413,8 @@ class ComposerView extends GetWidget { focusNode: controller.subjectEmailInputFocusNode, textController: controller.subjectEmailInputController, onTextChange: controller.setSubjectEmail, - padding: ComposerStyle.mobileSubjectPadding, - margin: ComposerStyle.mobileSubjectMargin, + padding: ComposerStyle.desktopSubjectPadding, + margin: ComposerStyle.desktopSubjectMargin, ), Expanded( child: LayoutBuilder( @@ -174,64 +424,97 @@ class ComposerView extends GetWidget { Column( children: [ Expanded( - child: Padding( - padding: ComposerStyle.mobileEditorPadding, - child: Obx(() => WebEditorView( - editorController: controller.richTextWebController!.editorController, - arguments: controller.composerArguments.value, - contentViewState: controller.emailContentsViewState.value, - currentWebContent: controller.textEditorWeb, - onInitial: controller.handleInitHtmlEditorWeb, - onChangeContent: controller.onChangeTextEditorWeb, - onFocus: controller.handleOnFocusHtmlEditorWeb, - onMouseDown: controller.handleOnMouseDownHtmlEditorWeb, - onEditorSettings: controller.richTextWebController!.onEditorSettingsChange, - onEditorTextSizeChanged: controller.richTextWebController!.onEditorTextSizeChanged, - width: constraints.maxWidth, - height: constraints.maxHeight, - onDragEnter: controller.handleOnDragEnterHtmlEditorWeb, - onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction( - context: context, - maxWidth: constraintsEditor.maxWidth, - listFileUpload: listFileUpload + child: Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: ComposerStyle.borderColor, + width: 1 + ) ), - onPasteImageFailureAction: (listFileUpload, base64, uploadError) => - controller.handleOnPasteImageFailureAction( - context: context, - listFileUpload: listFileUpload, - base64: base64, - uploadError: uploadError + color: ComposerStyle.backgroundEditorColor + ), + child: Column( + children: [ + Expanded( + child: Padding( + padding: ComposerStyle.desktopEditorPadding, + child: Obx(() { + return WebEditorView( + editorController: controller.richTextWebController!.editorController, + arguments: controller.composerArguments.value, + contentViewState: controller.emailContentsViewState.value, + currentWebContent: controller.textEditorWeb, + onInitial: controller.handleInitHtmlEditorWeb, + onChangeContent: controller.onChangeTextEditorWeb, + onFocus: controller.handleOnFocusHtmlEditorWeb, + onUnFocus: controller.handleOnUnFocusEditorWeb, + onMouseDown: controller.handleOnMouseDownHtmlEditorWeb, + onEditorSettings: controller.richTextWebController?.onEditorSettingsChange, + onEditorTextSizeChanged: controller.richTextWebController?.onEditorTextSizeChanged, + width: constraints.maxWidth, + height: constraints.maxHeight, + onDragEnter: controller.handleOnDragEnterHtmlEditorWeb, + onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction( + context: context, + maxWidth: constraintsEditor.maxWidth, + listFileUpload: listFileUpload + ), + onPasteImageFailureAction: (listFileUpload, base64, uploadError) => + controller.handleOnPasteImageFailureAction( + context: context, + listFileUpload: listFileUpload, + base64: base64, + uploadError: uploadError + ), + onInitialContentLoadComplete: controller.restoreCollapsibleButton, + ); + }), + ), ), - onInitialContentLoadComplete: controller.restoreCollapsibleButton, - )), + Obx(() { + if (controller.uploadController.listUploadAttachments.isNotEmpty) { + return AttachmentComposerWidget( + listFileUploaded: controller.uploadController.listUploadAttachments, + isCollapsed: controller.isAttachmentCollapsed, + onDeleteAttachmentAction: controller.deleteAttachmentUploaded, + onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed, + ); + } else { + return const SizedBox.shrink(); + } + }), + Obx(() { + if (controller.richTextWebController!.isFormattingOptionsEnabled) { + return ToolbarRichTextWebBuilder( + richTextWebController: controller.richTextWebController!, + padding: ComposerStyle.richToolbarPadding, + decoration: const BoxDecoration( + color: ComposerStyle.richToolbarColor, + boxShadow: ComposerStyle.richToolbarShadow + ), + ); + } else { + return const SizedBox.shrink(); + } + }) + ], + ), ), ), - Obx(() { - if (controller.uploadController.listUploadAttachments.isNotEmpty) { - return AttachmentComposerWidget( - listFileUploaded: controller.uploadController.listUploadAttachments, - isCollapsed: controller.isAttachmentCollapsed, - onDeleteAttachmentAction: controller.deleteAttachmentUploaded, - onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed, - ); - } else { - return const SizedBox.shrink(); - } - }), - Obx(() { - if (controller.richTextWebController!.isFormattingOptionsEnabled) { - return ToolbarRichTextWebBuilder( - richTextWebController: controller.richTextWebController!, - padding: ComposerStyle.richToolbarPadding, - decoration: const BoxDecoration( - color: ComposerStyle.richToolbarColor, - boxShadow: ComposerStyle.richToolbarShadow - ), - ); - } else { - return const SizedBox.shrink(); - } - }) + Obx(() => BottomBarComposerWidget( + isCodeViewEnabled: controller.richTextWebController!.codeViewEnabled, + isFormattingOptionsEnabled: controller.richTextWebController!.isFormattingOptionsEnabled, + hasReadReceipt: controller.hasRequestReadReceipt.value, + openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions, + attachFileAction: () => controller.openFilePickerByType(context, FileType.any), + insertImageAction: () => controller.insertImage(context, constraints.maxWidth), + showCodeViewAction: controller.richTextWebController!.toggleCodeView, + deleteComposerAction: () => controller.handleClickDeleteComposer(context), + saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context), + sendMessageAction: () => controller.handleClickSendButton(context), + requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context), + )), ], ), Align( @@ -284,152 +567,157 @@ class ComposerView extends GetWidget { } ), ), - ] + ]), ), ); - } - ), - desktop: Obx(() => DesktopResponsiveContainerView( + }, + displayMode: controller.screenDisplayMode.value, + emailSubject: controller.subjectEmail.value ?? '', + onCloseViewAction: () => controller.handleClickCloseComposer(context), + onChangeDisplayModeAction: controller.displayScreenTypeComposerAction, + )), + tablet: TabletResponsiveContainerView( childBuilder: (context, constraints) { return GestureDetector( onTap: () => controller.clearFocus(context), - child: Column(children: [ - Obx(() => DesktopAppBarComposerWidget( - emailSubject: controller.subjectEmail.value ?? '', - displayMode: controller.screenDisplayMode.value, - onCloseViewAction: () => controller.handleClickCloseComposer(context), - onChangeDisplayModeAction: controller.displayScreenTypeComposerAction, - constraints: constraints, - )), - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: ComposerStyle.getMaxHeightEmailAddressWidget( - context, - constraints, - controller.responsiveUtils - ) - ), - child: SingleChildScrollView( - controller: controller.scrollControllerEmailAddress, - child: Obx(() => Column( - children: [ - if (controller.fromRecipientState.value == PrefixRecipientState.enabled) - FromComposerDropDownWidget( - items: controller.listFromIdentities, - itemSelected: controller.identitySelected.value, - dropdownKey: controller.identityDropdownKey, - imagePaths: controller.imagePaths, - padding: ComposerStyle.desktopRecipientPadding, - margin: ComposerStyle.desktopRecipientMargin, - onChangeIdentity: controller.onChangeIdentity, - ), - RecipientComposerWidget( - prefix: PrefixEmailAddress.to, - listEmailAddress: controller.listToEmailAddress, - imagePaths: controller.imagePaths, - maxWidth: constraints.maxWidth, - fromState: controller.fromRecipientState.value, - ccState: controller.ccRecipientState.value, - bccState: controller.bccRecipientState.value, - expandMode: controller.toAddressExpandMode.value, - controller: controller.toEmailAddressController, - focusNode: controller.toAddressFocusNode, - focusNodeKeyboard: controller.toAddressFocusNodeKeyboard, - keyTagEditor: controller.keyToEmailTagEditor, - isInitial: controller.isInitialRecipient.value, - padding: ComposerStyle.desktopRecipientPadding, - margin: ComposerStyle.desktopRecipientMargin, - nextFocusNode: controller.getNextFocusOfToEmailAddress(), - onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onAddEmailAddressTypeAction: controller.addEmailAddressType, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - onFocusNextAddressAction: controller.handleFocusNextAddressAction, - onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, - ), - if (controller.ccRecipientState.value == PrefixRecipientState.enabled) - RecipientComposerWidget( - prefix: PrefixEmailAddress.cc, - listEmailAddress: controller.listCcEmailAddress, - imagePaths: controller.imagePaths, - maxWidth: constraints.maxWidth, - expandMode: controller.ccAddressExpandMode.value, - controller: controller.ccEmailAddressController, - focusNode: controller.ccAddressFocusNode, - focusNodeKeyboard: controller.ccAddressFocusNodeKeyboard, - keyTagEditor: controller.keyCcEmailTagEditor, - isInitial: controller.isInitialRecipient.value, - nextFocusNode: controller.getNextFocusOfCcEmailAddress(), - padding: ComposerStyle.desktopRecipientPadding, - margin: ComposerStyle.desktopRecipientMargin, - onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onDeleteEmailAddressTypeAction: controller.deleteEmailAddressType, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - onFocusNextAddressAction: controller.handleFocusNextAddressAction, - onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, - ), - if (controller.bccRecipientState.value == PrefixRecipientState.enabled) + excludeFromSemantics: true, + child: Semantics( + identifier: ComposerKeyValues.composerView, + container: true, + child: Column(children: [ + Obx(() => DesktopAppBarComposerWidget( + emailSubject: controller.subjectEmail.value ?? '', + onCloseViewAction: () => controller.handleClickCloseComposer(context), + constraints: constraints, + )), + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: ComposerStyle.getMaxHeightEmailAddressWidget( + context, + constraints, + controller.responsiveUtils + ) + ), + child: SingleChildScrollView( + controller: controller.scrollControllerEmailAddress, + child: Obx(() => Column( + children: [ + if (controller.fromRecipientState.value == PrefixRecipientState.enabled) + FromComposerDropDownWidget( + items: controller.listFromIdentities, + itemSelected: controller.identitySelected.value, + dropdownKey: controller.identityDropdownKey, + imagePaths: controller.imagePaths, + padding: ComposerStyle.tabletRecipientPadding, + margin: ComposerStyle.tabletRecipientMargin, + onChangeIdentity: controller.onChangeIdentity, + ), RecipientComposerWidget( - prefix: PrefixEmailAddress.bcc, - listEmailAddress: controller.listBccEmailAddress, + prefix: PrefixEmailAddress.to, + listEmailAddress: controller.listToEmailAddress, imagePaths: controller.imagePaths, maxWidth: constraints.maxWidth, - expandMode: controller.bccAddressExpandMode.value, - controller: controller.bccEmailAddressController, - focusNode: controller.bccAddressFocusNode, - focusNodeKeyboard: controller.bccAddressFocusNodeKeyboard, - keyTagEditor: controller.keyBccEmailTagEditor, + fromState: controller.fromRecipientState.value, + ccState: controller.ccRecipientState.value, + bccState: controller.bccRecipientState.value, + expandMode: controller.toAddressExpandMode.value, + controller: controller.toEmailAddressController, + focusNode: controller.toAddressFocusNode, + focusNodeKeyboard: controller.toAddressFocusNodeKeyboard, + keyTagEditor: controller.keyToEmailTagEditor, isInitial: controller.isInitialRecipient.value, - nextFocusNode: controller.subjectEmailInputFocusNode, - padding: ComposerStyle.desktopRecipientPadding, - margin: ComposerStyle.desktopRecipientMargin, + padding: ComposerStyle.tabletRecipientPadding, + margin: ComposerStyle.tabletRecipientMargin, + nextFocusNode: controller.getNextFocusOfToEmailAddress(), onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onDeleteEmailAddressTypeAction: controller.deleteEmailAddressType, + onAddEmailAddressTypeAction: controller.addEmailAddressType, onUpdateListEmailAddressAction: controller.updateListEmailAddress, onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, onFocusNextAddressAction: controller.handleFocusNextAddressAction, onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, ), - ], - )), - ) - ), - SubjectComposerWidget( - focusNode: controller.subjectEmailInputFocusNode, - textController: controller.subjectEmailInputController, - onTextChange: controller.setSubjectEmail, - padding: ComposerStyle.desktopSubjectPadding, - margin: ComposerStyle.desktopSubjectMargin, - ), - Expanded( - child: LayoutBuilder( - builder: (context, constraintsEditor) { - return Stack( - children: [ - Column( - children: [ - Expanded( - child: Container( - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: ComposerStyle.borderColor, - width: 1 - ) + if (controller.ccRecipientState.value == PrefixRecipientState.enabled) + RecipientComposerWidget( + prefix: PrefixEmailAddress.cc, + listEmailAddress: controller.listCcEmailAddress, + imagePaths: controller.imagePaths, + maxWidth: constraints.maxWidth, + expandMode: controller.ccAddressExpandMode.value, + controller: controller.ccEmailAddressController, + focusNode: controller.ccAddressFocusNode, + focusNodeKeyboard: controller.ccAddressFocusNodeKeyboard, + keyTagEditor: controller.keyCcEmailTagEditor, + isInitial: controller.isInitialRecipient.value, + nextFocusNode: controller.getNextFocusOfCcEmailAddress(), + padding: ComposerStyle.tabletRecipientPadding, + margin: ComposerStyle.tabletRecipientMargin, + onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, + onShowFullListEmailAddressAction: controller.showFullEmailAddress, + onDeleteEmailAddressTypeAction: controller.deleteEmailAddressType, + onUpdateListEmailAddressAction: controller.updateListEmailAddress, + onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, + onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, + ), + if (controller.bccRecipientState.value == PrefixRecipientState.enabled) + RecipientComposerWidget( + prefix: PrefixEmailAddress.bcc, + listEmailAddress: controller.listBccEmailAddress, + imagePaths: controller.imagePaths, + maxWidth: constraints.maxWidth, + expandMode: controller.bccAddressExpandMode.value, + controller: controller.bccEmailAddressController, + focusNode: controller.bccAddressFocusNode, + focusNodeKeyboard: controller.bccAddressFocusNodeKeyboard, + keyTagEditor: controller.keyBccEmailTagEditor, + isInitial: controller.isInitialRecipient.value, + nextFocusNode: controller.subjectEmailInputFocusNode, + padding: ComposerStyle.tabletRecipientPadding, + margin: ComposerStyle.tabletRecipientMargin, + onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, + onShowFullListEmailAddressAction: controller.showFullEmailAddress, + onDeleteEmailAddressTypeAction: controller.deleteEmailAddressType, + onUpdateListEmailAddressAction: controller.updateListEmailAddress, + onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, + onFocusNextAddressAction: controller.handleFocusNextAddressAction, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, + ), + ], + )), + ) + ), + SubjectComposerWidget( + focusNode: controller.subjectEmailInputFocusNode, + textController: controller.subjectEmailInputController, + onTextChange: controller.setSubjectEmail, + padding: ComposerStyle.tabletSubjectPadding, + margin: ComposerStyle.tabletSubjectMargin, + ), + Expanded( + child: LayoutBuilder( + builder: (context, constraintsBody) { + return Stack( + children: [ + Column( + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: ComposerStyle.borderColor, + width: 1 + ) + ), + color: ComposerStyle.backgroundEditorColor ), - color: ComposerStyle.backgroundEditorColor - ), - child: Column( - children: [ - Expanded( - child: Padding( - padding: ComposerStyle.desktopEditorPadding, - child: Obx(() { - return WebEditorView( + child: Column( + children: [ + Expanded( + child: Padding( + padding: ComposerStyle.tabletEditorPadding, + child: Obx(() => WebEditorView( editorController: controller.richTextWebController!.editorController, arguments: controller.composerArguments.value, contentViewState: controller.emailContentsViewState.value, @@ -437,15 +725,16 @@ class ComposerView extends GetWidget { onInitial: controller.handleInitHtmlEditorWeb, onChangeContent: controller.onChangeTextEditorWeb, onFocus: controller.handleOnFocusHtmlEditorWeb, + onUnFocus: controller.handleOnUnFocusEditorWeb, onMouseDown: controller.handleOnMouseDownHtmlEditorWeb, - onEditorSettings: controller.richTextWebController?.onEditorSettingsChange, - onEditorTextSizeChanged: controller.richTextWebController?.onEditorTextSizeChanged, + onEditorSettings: controller.richTextWebController!.onEditorSettingsChange, + onEditorTextSizeChanged: controller.richTextWebController!.onEditorTextSizeChanged, width: constraints.maxWidth, height: constraints.maxHeight, onDragEnter: controller.handleOnDragEnterHtmlEditorWeb, onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction( context: context, - maxWidth: constraintsEditor.maxWidth, + maxWidth: constraintsBody.maxWidth, listFileUpload: listFileUpload ), onPasteImageFailureAction: (listFileUpload, base64, uploadError) => @@ -456,376 +745,106 @@ class ComposerView extends GetWidget { uploadError: uploadError ), onInitialContentLoadComplete: controller.restoreCollapsibleButton, - ); - }), + )), + ), ), - ), - Obx(() { - if (controller.uploadController.listUploadAttachments.isNotEmpty) { - return AttachmentComposerWidget( - listFileUploaded: controller.uploadController.listUploadAttachments, - isCollapsed: controller.isAttachmentCollapsed, - onDeleteAttachmentAction: controller.deleteAttachmentUploaded, - onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed, - ); - } else { - return const SizedBox.shrink(); - } - }), - Obx(() { - if (controller.richTextWebController!.isFormattingOptionsEnabled) { - return ToolbarRichTextWebBuilder( - richTextWebController: controller.richTextWebController!, - padding: ComposerStyle.richToolbarPadding, - decoration: const BoxDecoration( - color: ComposerStyle.richToolbarColor, - boxShadow: ComposerStyle.richToolbarShadow - ), - ); - } else { - return const SizedBox.shrink(); - } - }) - ], + Obx(() { + if (controller.uploadController.listUploadAttachments.isNotEmpty) { + return AttachmentComposerWidget( + listFileUploaded: controller.uploadController.listUploadAttachments, + isCollapsed: controller.isAttachmentCollapsed, + onDeleteAttachmentAction: controller.deleteAttachmentUploaded, + onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed, + ); + } else { + return const SizedBox.shrink(); + } + }), + Obx(() { + if (controller.richTextWebController!.isFormattingOptionsEnabled) { + return ToolbarRichTextWebBuilder( + richTextWebController: controller.richTextWebController!, + padding: ComposerStyle.richToolbarPadding, + decoration: const BoxDecoration( + color: ComposerStyle.richToolbarColor, + boxShadow: ComposerStyle.richToolbarShadow + ), + ); + } else { + return const SizedBox.shrink(); + } + }) + ], + ), ), ), - ), - Obx(() => BottomBarComposerWidget( - isCodeViewEnabled: controller.richTextWebController!.codeViewEnabled, - isFormattingOptionsEnabled: controller.richTextWebController!.isFormattingOptionsEnabled, - hasReadReceipt: controller.hasRequestReadReceipt.value, - openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions, - attachFileAction: () => controller.openFilePickerByType(context, FileType.any), - insertImageAction: () => controller.insertImage(context, constraints.maxWidth), - showCodeViewAction: controller.richTextWebController!.toggleCodeView, - deleteComposerAction: () => controller.handleClickDeleteComposer(context), - saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context), - sendMessageAction: () => controller.handleClickSendButton(context), - requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context), + Obx(() => BottomBarComposerWidget( + isCodeViewEnabled: controller.richTextWebController!.codeViewEnabled, + isFormattingOptionsEnabled: controller.richTextWebController!.isFormattingOptionsEnabled, + hasReadReceipt: controller.hasRequestReadReceipt.value, + openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions, + attachFileAction: () => controller.openFilePickerByType(context, FileType.any), + insertImageAction: () => controller.insertImage(context, constraints.maxWidth), + showCodeViewAction: controller.richTextWebController!.toggleCodeView, + deleteComposerAction: () => controller.handleClickDeleteComposer(context), + saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context), + sendMessageAction: () => controller.handleClickSendButton(context), + requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context), + )), + ], + ), + Align( + alignment: AlignmentDirectional.topCenter, + child: Obx(() => InsertImageLoadingBarWidget( + uploadInlineViewState: controller.uploadController.uploadInlineViewState.value, + viewState: controller.viewState.value, + padding: ComposerStyle.insertImageLoadingBarPadding, )), - ], - ), - Align( - alignment: AlignmentDirectional.topCenter, - child: Obx(() => InsertImageLoadingBarWidget( - uploadInlineViewState: controller.uploadController.uploadInlineViewState.value, - viewState: controller.viewState.value, - padding: ComposerStyle.insertImageLoadingBarPadding, - )), - ), - Obx(() { - if (controller.mailboxDashBoardController.isAttachmentDraggableAppActive) { - return Positioned.fill( - child: PointerInterceptor( - child: AttachmentDropZoneWidget( - imagePaths: controller.imagePaths, - width: constraintsEditor.maxWidth, - height: constraintsEditor.maxHeight, - onAttachmentDropZoneListener: controller.onAttachmentDropZoneListener, - ) - ), - ); - } else { - return const SizedBox.shrink(); - } - }), - Obx(() { - if (controller.mailboxDashBoardController.isLocalFileDraggableAppActive) { - return Positioned.fill( - child: PointerInterceptor( - child: LocalFileDropZoneWidget( - imagePaths: controller.imagePaths, - width: constraintsEditor.maxWidth, - height: constraintsEditor.maxHeight, - onLocalFileDropZoneListener: (details) => - controller.onLocalFileDropZoneListener( - context: context, - details: details, - maxWidth: constraintsEditor.maxWidth, - ), - ) - ), - ); - } else { - return const SizedBox.shrink(); - } - }), - ], - ); - } - ), - ), - ]), - ); - }, - displayMode: controller.screenDisplayMode.value, - emailSubject: controller.subjectEmail.value ?? '', - onCloseViewAction: () => controller.handleClickCloseComposer(context), - onChangeDisplayModeAction: controller.displayScreenTypeComposerAction, - )), - tablet: TabletResponsiveContainerView( - childBuilder: (context, constraints) { - return GestureDetector( - onTap: () => controller.clearFocus(context), - child: Column(children: [ - Obx(() => DesktopAppBarComposerWidget( - emailSubject: controller.subjectEmail.value ?? '', - onCloseViewAction: () => controller.handleClickCloseComposer(context), - constraints: constraints, - )), - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: ComposerStyle.getMaxHeightEmailAddressWidget( - context, - constraints, - controller.responsiveUtils - ) - ), - child: SingleChildScrollView( - controller: controller.scrollControllerEmailAddress, - child: Obx(() => Column( - children: [ - if (controller.fromRecipientState.value == PrefixRecipientState.enabled) - FromComposerDropDownWidget( - items: controller.listFromIdentities, - itemSelected: controller.identitySelected.value, - dropdownKey: controller.identityDropdownKey, - imagePaths: controller.imagePaths, - padding: ComposerStyle.tabletRecipientPadding, - margin: ComposerStyle.tabletRecipientMargin, - onChangeIdentity: controller.onChangeIdentity, - ), - RecipientComposerWidget( - prefix: PrefixEmailAddress.to, - listEmailAddress: controller.listToEmailAddress, - imagePaths: controller.imagePaths, - maxWidth: constraints.maxWidth, - fromState: controller.fromRecipientState.value, - ccState: controller.ccRecipientState.value, - bccState: controller.bccRecipientState.value, - expandMode: controller.toAddressExpandMode.value, - controller: controller.toEmailAddressController, - focusNode: controller.toAddressFocusNode, - focusNodeKeyboard: controller.toAddressFocusNodeKeyboard, - keyTagEditor: controller.keyToEmailTagEditor, - isInitial: controller.isInitialRecipient.value, - padding: ComposerStyle.tabletRecipientPadding, - margin: ComposerStyle.tabletRecipientMargin, - nextFocusNode: controller.getNextFocusOfToEmailAddress(), - onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onAddEmailAddressTypeAction: controller.addEmailAddressType, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - onFocusNextAddressAction: controller.handleFocusNextAddressAction, - onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, - ), - if (controller.ccRecipientState.value == PrefixRecipientState.enabled) - RecipientComposerWidget( - prefix: PrefixEmailAddress.cc, - listEmailAddress: controller.listCcEmailAddress, - imagePaths: controller.imagePaths, - maxWidth: constraints.maxWidth, - expandMode: controller.ccAddressExpandMode.value, - controller: controller.ccEmailAddressController, - focusNode: controller.ccAddressFocusNode, - focusNodeKeyboard: controller.ccAddressFocusNodeKeyboard, - keyTagEditor: controller.keyCcEmailTagEditor, - isInitial: controller.isInitialRecipient.value, - nextFocusNode: controller.getNextFocusOfCcEmailAddress(), - padding: ComposerStyle.tabletRecipientPadding, - margin: ComposerStyle.tabletRecipientMargin, - onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onDeleteEmailAddressTypeAction: controller.deleteEmailAddressType, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - onFocusNextAddressAction: controller.handleFocusNextAddressAction, - onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, - ), - if (controller.bccRecipientState.value == PrefixRecipientState.enabled) - RecipientComposerWidget( - prefix: PrefixEmailAddress.bcc, - listEmailAddress: controller.listBccEmailAddress, - imagePaths: controller.imagePaths, - maxWidth: constraints.maxWidth, - expandMode: controller.bccAddressExpandMode.value, - controller: controller.bccEmailAddressController, - focusNode: controller.bccAddressFocusNode, - focusNodeKeyboard: controller.bccAddressFocusNodeKeyboard, - keyTagEditor: controller.keyBccEmailTagEditor, - isInitial: controller.isInitialRecipient.value, - nextFocusNode: controller.subjectEmailInputFocusNode, - padding: ComposerStyle.tabletRecipientPadding, - margin: ComposerStyle.tabletRecipientMargin, - onFocusEmailAddressChangeAction: controller.onEmailAddressFocusChange, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onDeleteEmailAddressTypeAction: controller.deleteEmailAddressType, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - onFocusNextAddressAction: controller.handleFocusNextAddressAction, - onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, - ), - ], - )), - ) - ), - SubjectComposerWidget( - focusNode: controller.subjectEmailInputFocusNode, - textController: controller.subjectEmailInputController, - onTextChange: controller.setSubjectEmail, - padding: ComposerStyle.tabletSubjectPadding, - margin: ComposerStyle.tabletSubjectMargin, - ), - Expanded( - child: LayoutBuilder( - builder: (context, constraintsBody) { - return Stack( - children: [ - Column( - children: [ - Expanded( - child: Container( - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: ComposerStyle.borderColor, - width: 1 - ) - ), - color: ComposerStyle.backgroundEditorColor + ), + Obx(() { + if (controller.mailboxDashBoardController.isAttachmentDraggableAppActive) { + return Positioned.fill( + child: PointerInterceptor( + child: AttachmentDropZoneWidget( + imagePaths: controller.imagePaths, + width: constraintsBody.maxWidth, + height: constraintsBody.maxHeight, + onAttachmentDropZoneListener: controller.onAttachmentDropZoneListener, + ) ), - child: Column( - children: [ - Expanded( - child: Padding( - padding: ComposerStyle.tabletEditorPadding, - child: Obx(() => WebEditorView( - editorController: controller.richTextWebController!.editorController, - arguments: controller.composerArguments.value, - contentViewState: controller.emailContentsViewState.value, - currentWebContent: controller.textEditorWeb, - onInitial: controller.handleInitHtmlEditorWeb, - onChangeContent: controller.onChangeTextEditorWeb, - onFocus: controller.handleOnFocusHtmlEditorWeb, - onMouseDown: controller.handleOnMouseDownHtmlEditorWeb, - onEditorSettings: controller.richTextWebController!.onEditorSettingsChange, - onEditorTextSizeChanged: controller.richTextWebController!.onEditorTextSizeChanged, - width: constraints.maxWidth, - height: constraints.maxHeight, - onDragEnter: controller.handleOnDragEnterHtmlEditorWeb, - onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction( - context: context, - maxWidth: constraintsBody.maxWidth, - listFileUpload: listFileUpload - ), - onPasteImageFailureAction: (listFileUpload, base64, uploadError) => - controller.handleOnPasteImageFailureAction( - context: context, - listFileUpload: listFileUpload, - base64: base64, - uploadError: uploadError - ), - onInitialContentLoadComplete: controller.restoreCollapsibleButton, - )), + ); + } else { + return const SizedBox.shrink(); + } + }), + Obx(() { + if (controller.mailboxDashBoardController.isLocalFileDraggableAppActive) { + return Positioned.fill( + child: PointerInterceptor( + child: LocalFileDropZoneWidget( + imagePaths: controller.imagePaths, + width: constraintsBody.maxWidth, + height: constraintsBody.maxHeight, + onLocalFileDropZoneListener: (details) => + controller.onLocalFileDropZoneListener( + context: context, + details: details, + maxWidth: constraintsBody.maxWidth, ), - ), - Obx(() { - if (controller.uploadController.listUploadAttachments.isNotEmpty) { - return AttachmentComposerWidget( - listFileUploaded: controller.uploadController.listUploadAttachments, - isCollapsed: controller.isAttachmentCollapsed, - onDeleteAttachmentAction: controller.deleteAttachmentUploaded, - onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed, - ); - } else { - return const SizedBox.shrink(); - } - }), - Obx(() { - if (controller.richTextWebController!.isFormattingOptionsEnabled) { - return ToolbarRichTextWebBuilder( - richTextWebController: controller.richTextWebController!, - padding: ComposerStyle.richToolbarPadding, - decoration: const BoxDecoration( - color: ComposerStyle.richToolbarColor, - boxShadow: ComposerStyle.richToolbarShadow - ), - ); - } else { - return const SizedBox.shrink(); - } - }) - ], + ) ), - ), - ), - Obx(() => BottomBarComposerWidget( - isCodeViewEnabled: controller.richTextWebController!.codeViewEnabled, - isFormattingOptionsEnabled: controller.richTextWebController!.isFormattingOptionsEnabled, - hasReadReceipt: controller.hasRequestReadReceipt.value, - openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions, - attachFileAction: () => controller.openFilePickerByType(context, FileType.any), - insertImageAction: () => controller.insertImage(context, constraints.maxWidth), - showCodeViewAction: controller.richTextWebController!.toggleCodeView, - deleteComposerAction: () => controller.handleClickDeleteComposer(context), - saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context), - sendMessageAction: () => controller.handleClickSendButton(context), - requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context), - )), - ], - ), - Align( - alignment: AlignmentDirectional.topCenter, - child: Obx(() => InsertImageLoadingBarWidget( - uploadInlineViewState: controller.uploadController.uploadInlineViewState.value, - viewState: controller.viewState.value, - padding: ComposerStyle.insertImageLoadingBarPadding, - )), - ), - Obx(() { - if (controller.mailboxDashBoardController.isAttachmentDraggableAppActive) { - return Positioned.fill( - child: PointerInterceptor( - child: AttachmentDropZoneWidget( - imagePaths: controller.imagePaths, - width: constraintsBody.maxWidth, - height: constraintsBody.maxHeight, - onAttachmentDropZoneListener: controller.onAttachmentDropZoneListener, - ) - ), - ); - } else { - return const SizedBox.shrink(); - } - }), - Obx(() { - if (controller.mailboxDashBoardController.isLocalFileDraggableAppActive) { - return Positioned.fill( - child: PointerInterceptor( - child: LocalFileDropZoneWidget( - imagePaths: controller.imagePaths, - width: constraintsBody.maxWidth, - height: constraintsBody.maxHeight, - onLocalFileDropZoneListener: (details) => - controller.onLocalFileDropZoneListener( - context: context, - details: details, - maxWidth: constraintsBody.maxWidth, - ), - ) - ), - ); - } else { - return const SizedBox.shrink(); - } - }), - ], - ); - }, - ), - ) - ]), + ); + } else { + return const SizedBox.shrink(); + } + }), + ], + ); + }, + ), + ) + ]), + ), ); }, ) @@ -835,62 +854,82 @@ class ComposerView extends GetWidget { List _createMoreOptionPopupItems(BuildContext context) { return [ PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, - child: PopupItemWidget( - controller.imagePaths.icStyleCodeView, - AppLocalizations.of(context).embedCode, - styleName: ComposerStyle.popupItemTextStyle, - colorIcon: ComposerStyle.popupItemIconColor, - padding: ComposerStyle.popupItemPadding, - selectedIcon: controller.imagePaths.icFilterSelected, - isSelected: controller.richTextWebController?.codeViewEnabled, - onCallbackAction: () { - popBack(); - controller.richTextWebController?.toggleCodeView(); - } + child: Semantics( + container: true, + identifier: ComposerKeyValues.toggleCodeViewButton, + child: PopupItemWidget( + controller.imagePaths.icStyleCodeView, + AppLocalizations.of(context).embedCode, + styleName: ComposerStyle.popupItemTextStyle, + colorIcon: ComposerStyle.popupItemIconColor, + padding: ComposerStyle.popupItemPadding, + selectedIcon: controller.imagePaths.icFilterSelected, + isSelected: controller.richTextWebController?.codeViewEnabled, + onCallbackAction: () { + popBack(); + controller.richTextWebController?.toggleCodeView(); + } + ), ) ), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, - child: PopupItemWidget( - controller.imagePaths.icReadReceipt, - AppLocalizations.of(context).requestReadReceipt, - styleName: ComposerStyle.popupItemTextStyle, - padding: ComposerStyle.popupItemPadding, - colorIcon: ComposerStyle.popupItemIconColor, - selectedIcon: controller.imagePaths.icFilterSelected, - isSelected: controller.hasRequestReadReceipt.value, - onCallbackAction: () { - popBack(); - controller.toggleRequestReadReceipt(context); - } + child: Semantics( + container: true, + identifier: ComposerKeyValues.toggleReadRecipientsButton, + child: PopupItemWidget( + controller.imagePaths.icReadReceipt, + AppLocalizations.of(context).requestReadReceipt, + styleName: ComposerStyle.popupItemTextStyle, + padding: ComposerStyle.popupItemPadding, + colorIcon: ComposerStyle.popupItemIconColor, + selectedIcon: controller.imagePaths.icFilterSelected, + isSelected: controller.hasRequestReadReceipt.value, + onCallbackAction: () { + popBack(); + controller.toggleRequestReadReceipt(context); + } + ), ) ), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, - child: PopupItemWidget( - controller.imagePaths.icSaveToDraft, - AppLocalizations.of(context).saveAsDraft, - colorIcon: ComposerStyle.popupItemIconColor, - styleName: ComposerStyle.popupItemTextStyle, - padding: ComposerStyle.popupItemPadding, - onCallbackAction: () { - popBack(); - controller.handleClickSaveAsDraftsButton(context); - } + child: Semantics( + container: true, + identifier: ComposerKeyValues.saveAsDraftButton, + child: PopupItemWidget( + controller.imagePaths.icSaveToDraft, + AppLocalizations.of(context).saveAsDraft, + colorIcon: ComposerStyle.popupItemIconColor, + styleName: ComposerStyle.popupItemTextStyle, + padding: ComposerStyle.popupItemPadding, + onCallbackAction: () { + popBack(); + controller.handleClickSaveAsDraftsButton(context); + } + ), ) ), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, - child: PopupItemWidget( - controller.imagePaths.icDeleteMailbox, - AppLocalizations.of(context).delete, - styleName: ComposerStyle.popupItemTextStyle, - padding: ComposerStyle.popupItemPadding, - onCallbackAction: () { - popBack(); - controller.handleClickDeleteComposer(context); - }, + child: Semantics( + container: true, + identifier: ComposerKeyValues.deleteComposedEmailButton, + child: PopupItemWidget( + controller.imagePaths.icDeleteMailbox, + AppLocalizations.of(context).delete, + styleName: ComposerStyle.popupItemTextStyle, + padding: ComposerStyle.popupItemPadding, + onCallbackAction: () { + popBack(); + controller.handleClickDeleteComposer(context); + }, + ), ) ), ]; diff --git a/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart b/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart index 484c277cf2..c106989c12 100644 --- a/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart +++ b/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart @@ -113,6 +113,7 @@ mixin RichTextButtonMixin { Color? color, String? tooltip, double opacity = 1.0, + bool excludeFromSemantics = false, }){ final newColor = color == Colors.white ? AppColor.colorDefaultRichTextButton @@ -123,6 +124,7 @@ mixin RichTextButtonMixin { message: tooltip, child: SvgPicture.asset( path, + excludeFromSemantics: excludeFromSemantics, colorFilter: newColor?.withOpacity(opacity).asFilter(), fit: BoxFit.fill)) : SvgPicture.asset( diff --git a/lib/features/composer/presentation/model/order_list_type.dart b/lib/features/composer/presentation/model/order_list_type.dart index 57e4b4fb75..0dab72c13a 100644 --- a/lib/features/composer/presentation/model/order_list_type.dart +++ b/lib/features/composer/presentation/model/order_list_type.dart @@ -2,6 +2,7 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; enum OrderListType { @@ -49,12 +50,16 @@ enum OrderListType { ImagePaths imagePaths, Function(OrderListType type) onActionCallback ) { - return buildIconWeb( - icon: SvgPicture.asset(getIcon(imagePaths)), - iconPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 5), - minSize: 30, - iconSize: 30, - tooltip: getTooltipButton(context), - onTap: () => onActionCallback.call(this)); + return Semantics( + identifier: ComposerKeyValues.richtextListStyleOption, + container: true, + child: buildIconWeb( + icon: SvgPicture.asset(getIcon(imagePaths)), + iconPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 5), + minSize: 30, + iconSize: 30, + tooltip: getTooltipButton(context), + onTap: () => onActionCallback.call(this)), + ); } } \ No newline at end of file diff --git a/lib/features/composer/presentation/model/paragraph_type.dart b/lib/features/composer/presentation/model/paragraph_type.dart index c85238fbf0..0217feb9a7 100644 --- a/lib/features/composer/presentation/model/paragraph_type.dart +++ b/lib/features/composer/presentation/model/paragraph_type.dart @@ -2,6 +2,7 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; enum ParagraphType { @@ -85,12 +86,16 @@ enum ParagraphType { ImagePaths imagePaths, Function(ParagraphType paragraph) onActionCallback ) { - return buildIconWeb( - icon: SvgPicture.asset(getIcon(imagePaths)), - iconPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 5), - minSize: 30, - iconSize: 30, - tooltip: getTooltipButton(context), - onTap: () => onActionCallback.call(this)); + return Semantics( + identifier: ComposerKeyValues.richtextAlignParagraphOption, + container: true, + child: buildIconWeb( + icon: SvgPicture.asset(getIcon(imagePaths)), + iconPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 5), + minSize: 30, + iconSize: 30, + tooltip: getTooltipButton(context), + onTap: () => onActionCallback.call(this)), + ); } } \ No newline at end of file diff --git a/lib/features/composer/presentation/view/web/web_editor_view.dart b/lib/features/composer/presentation/view/web/web_editor_view.dart index f04efac66e..5c9b662d97 100644 --- a/lib/features/composer/presentation/view/web/web_editor_view.dart +++ b/lib/features/composer/presentation/view/web/web_editor_view.dart @@ -7,6 +7,7 @@ import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; import 'package:html_editor_enhanced/html_editor.dart'; import 'package:model/email/email_action_type.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/domain/state/restore_email_inline_images_state.dart'; import 'package:tmail_ui_user/features/composer/presentation/view/editor_view_mixin.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/web/web_editor_widget.dart'; @@ -66,35 +67,10 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { case EmailActionType.compose: case EmailActionType.composeFromEmailAddress: case EmailActionType.composeFromFileShared: - return WebEditorWidget( - editorController: editorController, - content: currentWebContent ?? HtmlExtension.editorStartTags, - direction: AppUtils.getCurrentDirection(context), - onInitial: onInitial, - onChangeContent: onChangeContent, - onFocus: onFocus, - onUnFocus: onUnFocus, - onMouseDown: onMouseDown, - onEditorSettings: onEditorSettings, - onEditorTextSizeChanged: onEditorTextSizeChanged, - width: width, - height: height, - onDragEnter: onDragEnter, - onPasteImageSuccessAction: onPasteImageSuccessAction, - onPasteImageFailureAction: onPasteImageFailureAction, - onInitialContentLoadComplete: onInitialContentLoadComplete, - ); - case EmailActionType.editDraft: - case EmailActionType.editSendingEmail: - case EmailActionType.composeFromContentShared: - case EmailActionType.reopenComposerBrowser: - case EmailActionType.composeFromUnsubscribeMailtoLink: - case EmailActionType.composeFromMailtoUri: - if (contentViewState == null) { - return const SizedBox.shrink(); - } - return contentViewState!.fold( - (failure) => WebEditorWidget( + return Semantics( + identifier: ComposerKeyValues.composerContentField, + container: true, + child: WebEditorWidget( editorController: editorController, content: currentWebContent ?? HtmlExtension.editorStartTags, direction: AppUtils.getCurrentDirection(context), @@ -112,6 +88,39 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onPasteImageFailureAction: onPasteImageFailureAction, onInitialContentLoadComplete: onInitialContentLoadComplete, ), + ); + case EmailActionType.editDraft: + case EmailActionType.editSendingEmail: + case EmailActionType.composeFromContentShared: + case EmailActionType.reopenComposerBrowser: + case EmailActionType.composeFromUnsubscribeMailtoLink: + case EmailActionType.composeFromMailtoUri: + if (contentViewState == null) { + return const SizedBox.shrink(); + } + return contentViewState!.fold( + (failure) => Semantics( + identifier: ComposerKeyValues.composerContentField, + container: true, + child: WebEditorWidget( + editorController: editorController, + content: currentWebContent ?? HtmlExtension.editorStartTags, + direction: AppUtils.getCurrentDirection(context), + onInitial: onInitial, + onChangeContent: onChangeContent, + onFocus: onFocus, + onUnFocus: onUnFocus, + onMouseDown: onMouseDown, + onEditorSettings: onEditorSettings, + onEditorTextSizeChanged: onEditorTextSizeChanged, + width: width, + height: height, + onDragEnter: onDragEnter, + onPasteImageSuccessAction: onPasteImageSuccessAction, + onPasteImageFailureAction: onPasteImageFailureAction, + onInitialContentLoadComplete: onInitialContentLoadComplete, + ), + ), (success) { if (success is GetEmailContentLoading || success is RestoringEmailInlineImages) { return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0)); @@ -122,23 +131,27 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { if (newContent.isEmpty) { newContent = HtmlExtension.editorStartTags; } - return WebEditorWidget( - editorController: editorController, - content: currentWebContent ?? newContent, - direction: AppUtils.getCurrentDirection(context), - onInitial: onInitial, - onChangeContent: onChangeContent, - onFocus: onFocus, - onUnFocus: onUnFocus, - onMouseDown: onMouseDown, - onEditorSettings: onEditorSettings, - onEditorTextSizeChanged: onEditorTextSizeChanged, - width: width, - height: height, - onDragEnter: onDragEnter, - onPasteImageSuccessAction: onPasteImageSuccessAction, - onPasteImageFailureAction: onPasteImageFailureAction, - onInitialContentLoadComplete: onInitialContentLoadComplete, + return Semantics( + identifier: ComposerKeyValues.composerContentField, + container: true, + child: WebEditorWidget( + editorController: editorController, + content: currentWebContent ?? newContent, + direction: AppUtils.getCurrentDirection(context), + onInitial: onInitial, + onChangeContent: onChangeContent, + onFocus: onFocus, + onUnFocus: onUnFocus, + onMouseDown: onMouseDown, + onEditorSettings: onEditorSettings, + onEditorTextSizeChanged: onEditorTextSizeChanged, + width: width, + height: height, + onDragEnter: onDragEnter, + onPasteImageSuccessAction: onPasteImageSuccessAction, + onPasteImageFailureAction: onPasteImageFailureAction, + onInitialContentLoadComplete: onInitialContentLoadComplete, + ), ); } } @@ -157,38 +170,10 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { emailActionType: arguments!.emailActionType, presentationEmail: arguments!.presentationEmail! ); - return WebEditorWidget( - editorController: editorController, - content: currentWebContent ?? emailContentQuoted, - direction: AppUtils.getCurrentDirection(context), - onInitial: onInitial, - onChangeContent: onChangeContent, - onFocus: onFocus, - onUnFocus: onUnFocus, - onMouseDown: onMouseDown, - onEditorSettings: onEditorSettings, - onEditorTextSizeChanged: onEditorTextSizeChanged, - width: width, - height: height, - onDragEnter: onDragEnter, - onPasteImageSuccessAction: onPasteImageSuccessAction, - onPasteImageFailureAction: onPasteImageFailureAction, - onInitialContentLoadComplete: onInitialContentLoadComplete, - ); - }, - (success) { - if (success is TransformHtmlEmailContentLoading) { - return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0)); - } else { - final emailContentQuoted = getEmailContentQuotedAsHtml( - context: context, - emailContent: success is TransformHtmlEmailContentSuccess - ? success.htmlContent - : '', - emailActionType: arguments!.emailActionType, - presentationEmail: arguments!.presentationEmail! - ); - return WebEditorWidget( + return Semantics( + identifier: ComposerKeyValues.composerContentField, + container: true, + child: WebEditorWidget( editorController: editorController, content: currentWebContent ?? emailContentQuoted, direction: AppUtils.getCurrentDirection(context), @@ -205,28 +190,68 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onPasteImageSuccessAction: onPasteImageSuccessAction, onPasteImageFailureAction: onPasteImageFailureAction, onInitialContentLoadComplete: onInitialContentLoadComplete, + ), + ); + }, + (success) { + if (success is TransformHtmlEmailContentLoading) { + return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0)); + } else { + final emailContentQuoted = getEmailContentQuotedAsHtml( + context: context, + emailContent: success is TransformHtmlEmailContentSuccess + ? success.htmlContent + : '', + emailActionType: arguments!.emailActionType, + presentationEmail: arguments!.presentationEmail! + ); + return Semantics( + identifier: ComposerKeyValues.composerContentField, + container: true, + child: WebEditorWidget( + editorController: editorController, + content: currentWebContent ?? emailContentQuoted, + direction: AppUtils.getCurrentDirection(context), + onInitial: onInitial, + onChangeContent: onChangeContent, + onFocus: onFocus, + onUnFocus: onUnFocus, + onMouseDown: onMouseDown, + onEditorSettings: onEditorSettings, + onEditorTextSizeChanged: onEditorTextSizeChanged, + width: width, + height: height, + onDragEnter: onDragEnter, + onPasteImageSuccessAction: onPasteImageSuccessAction, + onPasteImageFailureAction: onPasteImageFailureAction, + onInitialContentLoadComplete: onInitialContentLoadComplete, + ), ); } } ); default: - return WebEditorWidget( - editorController: editorController, - content: currentWebContent ?? HtmlExtension.editorStartTags, - direction: AppUtils.getCurrentDirection(context), - onInitial: onInitial, - onChangeContent: onChangeContent, - onFocus: onFocus, - onUnFocus: onUnFocus, - onMouseDown: onMouseDown, - onEditorSettings: onEditorSettings, - onEditorTextSizeChanged: onEditorTextSizeChanged, - width: width, - height: height, - onDragEnter: onDragEnter, - onPasteImageSuccessAction: onPasteImageSuccessAction, - onPasteImageFailureAction: onPasteImageFailureAction, - onInitialContentLoadComplete: onInitialContentLoadComplete, + return Semantics( + identifier: ComposerKeyValues.composerContentField, + container: true, + child: WebEditorWidget( + editorController: editorController, + content: currentWebContent ?? HtmlExtension.editorStartTags, + direction: AppUtils.getCurrentDirection(context), + onInitial: onInitial, + onChangeContent: onChangeContent, + onFocus: onFocus, + onUnFocus: onUnFocus, + onMouseDown: onMouseDown, + onEditorSettings: onEditorSettings, + onEditorTextSizeChanged: onEditorTextSizeChanged, + width: width, + height: height, + onDragEnter: onDragEnter, + onPasteImageSuccessAction: onPasteImageSuccessAction, + onPasteImageFailureAction: onPasteImageFailureAction, + onInitialContentLoadComplete: onInitialContentLoadComplete, + ), ); } } diff --git a/lib/features/composer/presentation/widgets/drop_down_menu_header_style_widget.dart b/lib/features/composer/presentation/widgets/drop_down_menu_header_style_widget.dart index 357dd4a811..29ec1d7e2c 100644 --- a/lib/features/composer/presentation/widgets/drop_down_menu_header_style_widget.dart +++ b/lib/features/composer/presentation/widgets/drop_down_menu_header_style_widget.dart @@ -4,6 +4,7 @@ import 'package:core/presentation/utils/style_utils.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/header_style_type.dart'; class DropDownMenuHeaderStyleWidget extends StatelessWidget { @@ -34,12 +35,16 @@ class DropDownMenuHeaderStyleWidget extends StatelessWidget { items: items .map((item) => DropdownMenuItem( value: item, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: heightItem, - alignment: Alignment.centerLeft, - child: _buildItemDropdown(item), + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextHeaderStyleOption, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: heightItem, + alignment: Alignment.centerLeft, + child: _buildItemDropdown(item), + ), ), ), )) diff --git a/lib/features/composer/presentation/widgets/mobile/app_bar_composer_widget.dart b/lib/features/composer/presentation/widgets/mobile/app_bar_composer_widget.dart index 0200424ec8..0ae4394294 100644 --- a/lib/features/composer/presentation/widgets/mobile/app_bar_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/mobile/app_bar_composer_widget.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/mobile_app_bar_composer_widget_style.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -48,56 +49,76 @@ class AppBarComposerWidget extends StatelessWidget { onTapActionCallback: onCloseViewAction ), const Spacer(), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icRichToolbar, - iconColor: MobileAppBarComposerWidgetStyle.iconColor, - backgroundColor: Colors.transparent, - iconSize: MobileAppBarComposerWidgetStyle.richTextIconSize, - padding: MobileAppBarComposerWidgetStyle.richTextIconPadding, - tooltipMessage: AppLocalizations.of(context).formattingOptions, - onTapActionCallback: openRichToolbarAction, + Semantics( + container: true, + identifier: ComposerKeyValues.openTextFormattingMenuButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icRichToolbar, + iconColor: MobileAppBarComposerWidgetStyle.iconColor, + backgroundColor: Colors.transparent, + iconSize: MobileAppBarComposerWidgetStyle.richTextIconSize, + padding: MobileAppBarComposerWidgetStyle.richTextIconPadding, + tooltipMessage: AppLocalizations.of(context).formattingOptions, + onTapActionCallback: openRichToolbarAction, + ), ), if (isNetworkConnectionAvailable) ...[ const SizedBox(width: MobileAppBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icAttachFile, - iconColor: MobileAppBarComposerWidgetStyle.iconColor, - backgroundColor: Colors.transparent, - iconSize: MobileAppBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).attach_file, - onTapActionCallback: attachFileAction, + Semantics( + container: true, + identifier: ComposerKeyValues.pickAttachmentButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icAttachFile, + iconColor: MobileAppBarComposerWidgetStyle.iconColor, + backgroundColor: Colors.transparent, + iconSize: MobileAppBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).attach_file, + onTapActionCallback: attachFileAction, + ), ), const SizedBox(width: MobileAppBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icInsertImage, - iconColor: MobileAppBarComposerWidgetStyle.iconColor, - backgroundColor: Colors.transparent, - iconSize: MobileAppBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).insertImage, - onTapActionCallback: insertImageAction, + Semantics( + container: true, + identifier: ComposerKeyValues.pickInlineImageButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icInsertImage, + iconColor: MobileAppBarComposerWidgetStyle.iconColor, + backgroundColor: Colors.transparent, + iconSize: MobileAppBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).insertImage, + onTapActionCallback: insertImageAction, + ), ), const SizedBox(width: MobileAppBarComposerWidgetStyle.space), ], - TMailButtonWidget.fromIcon( - icon: isSendButtonEnabled - ? _imagePaths.icSendMobile - : _imagePaths.icSendDisable, - backgroundColor: Colors.transparent, - padding: MobileAppBarComposerWidgetStyle.iconPadding, - iconSize: MobileAppBarComposerWidgetStyle.sendButtonIconSize, - tooltipMessage: AppLocalizations.of(context).send, - onTapActionCallback: sendMessageAction, + Semantics( + container: true, + identifier: ComposerKeyValues.composerSendButton, + child: TMailButtonWidget.fromIcon( + icon: isSendButtonEnabled + ? _imagePaths.icSendMobile + : _imagePaths.icSendDisable, + backgroundColor: Colors.transparent, + padding: MobileAppBarComposerWidgetStyle.iconPadding, + iconSize: MobileAppBarComposerWidgetStyle.sendButtonIconSize, + tooltipMessage: AppLocalizations.of(context).send, + onTapActionCallback: sendMessageAction, + ), ), const SizedBox(width: MobileAppBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icMore, - iconColor: MobileAppBarComposerWidgetStyle.iconColor, - backgroundColor: Colors.transparent, - padding: MobileAppBarComposerWidgetStyle.iconPadding, - iconSize: MobileAppBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).more, - onTapActionAtPositionCallback: openContextMenuAction, + Semantics( + container: true, + identifier: ComposerKeyValues.openMobileMoreOptionsButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icMore, + iconColor: MobileAppBarComposerWidgetStyle.iconColor, + backgroundColor: Colors.transparent, + padding: MobileAppBarComposerWidgetStyle.iconPadding, + iconSize: MobileAppBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).more, + onTapActionAtPositionCallback: openContextMenuAction, + ), ), ], ), diff --git a/lib/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart b/lib/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart index f1cb040a90..63a2211dc2 100644 --- a/lib/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart +++ b/lib/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart @@ -10,6 +10,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/mobile/from_composer_bottom_sheet_style.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -155,58 +156,62 @@ class FromComposerBottomSheetBuilder { final Identity identity = _identities[index]; return Material( color: Colors.transparent, - child: InkWell( - borderRadius: FromComposerBottomSheetStyle.borderRadius, - onTap: () => _onChangeIdentity?.call(identity), - child: Padding( - padding: FromComposerBottomSheetStyle.searchTextInputPadding, - child: Row( - children: [ - Container( - width: FromComposerBottomSheetStyle.identityItemSize, - height: FromComposerBottomSheetStyle.identityItemSize, - alignment: Alignment.center, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.avatarColor, - border: Border.all( - color: AppColor.colorShadowBgContentEmail, - width: FromComposerBottomSheetStyle.identityItemBorderWidth - ) + child: Semantics( + container: true, + identifier: ComposerKeyValues.fromEmailSelectorOption, + child: InkWell( + borderRadius: FromComposerBottomSheetStyle.borderRadius, + onTap: () => _onChangeIdentity?.call(identity), + child: Padding( + padding: FromComposerBottomSheetStyle.searchTextInputPadding, + child: Row( + children: [ + Container( + width: FromComposerBottomSheetStyle.identityItemSize, + height: FromComposerBottomSheetStyle.identityItemSize, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.avatarColor, + border: Border.all( + color: AppColor.colorShadowBgContentEmail, + width: FromComposerBottomSheetStyle.identityItemBorderWidth + ) + ), + child: Text( + identity.name!.isNotEmpty + ? identity.name!.firstLetterToUpperCase + : identity.email!.firstLetterToUpperCase, + style: FromComposerBottomSheetStyle.searchBarTextStyle, + ), ), - child: Text( - identity.name!.isNotEmpty - ? identity.name!.firstLetterToUpperCase - : identity.email!.firstLetterToUpperCase, - style: FromComposerBottomSheetStyle.searchBarTextStyle, - ), - ), - const SizedBox(width: FromComposerBottomSheetStyle.identityItemSpace), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (identity.name!.isNotEmpty) - Text( - identity.name!, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - style: FromComposerBottomSheetStyle.identityItemTitleTextStyle, - ), - if (identity.email!.isNotEmpty) - Text( - identity.email!, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - style: FromComposerBottomSheetStyle.identityItemSubTitleTextStyle, - ) - ], - ), - ) - ], + const SizedBox(width: FromComposerBottomSheetStyle.identityItemSpace), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (identity.name!.isNotEmpty) + Text( + identity.name!, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: FromComposerBottomSheetStyle.identityItemTitleTextStyle, + ), + if (identity.email!.isNotEmpty) + Text( + identity.email!, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: FromComposerBottomSheetStyle.identityItemSubTitleTextStyle, + ) + ], + ), + ) + ], + ), ), ), ), diff --git a/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart b/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart index 98705e8d99..22ba47aff1 100644 --- a/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart +++ b/lib/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; import 'package:model/email/prefix_email_address.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/mobile/from_composer_mobile_widget_style.dart'; @@ -49,43 +50,47 @@ class FromComposerMobileWidget extends StatelessWidget { Flexible( child: Padding( padding: FromComposerMobileWidgetStyle.identityButtonInkWellPadding, - child: InkWell( - borderRadius: FromComposerMobileWidgetStyle.identityButtonInkWellBorderRadius, - onTap: onTap, - child: Container( - height: FromComposerMobileWidgetStyle.identityButtonHeight, - padding: FromComposerMobileWidgetStyle.identityButtonPadding, - decoration: FromComposerMobileWidgetStyle.identityButtonDecoration, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (selectedIdentity != null) - Flexible( - child: RichText( - maxLines: 1, - softWrap: false, - overflow: CommonTextStyle.defaultTextOverFlow, - text: TextSpan( - children: [ - if (selectedIdentity!.name!.isNotEmpty) - TextSpan( - text: '${selectedIdentity!.name} ', - style: FromComposerMobileWidgetStyle.buttonTitleTextStyle, - ), - TextSpan( - text: '(${selectedIdentity!.email})', - style: selectedIdentity!.name!.isNotEmpty - ? FromComposerMobileWidgetStyle.buttonSubTitleTextStyle - : FromComposerMobileWidgetStyle.buttonTitleTextStyle - ) - ] + child: Semantics( + container: true, + identifier: ComposerKeyValues.openFromEmailSelector, + child: InkWell( + borderRadius: FromComposerMobileWidgetStyle.identityButtonInkWellBorderRadius, + onTap: onTap, + child: Container( + height: FromComposerMobileWidgetStyle.identityButtonHeight, + padding: FromComposerMobileWidgetStyle.identityButtonPadding, + decoration: FromComposerMobileWidgetStyle.identityButtonDecoration, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (selectedIdentity != null) + Flexible( + child: RichText( + maxLines: 1, + softWrap: false, + overflow: CommonTextStyle.defaultTextOverFlow, + text: TextSpan( + children: [ + if (selectedIdentity!.name!.isNotEmpty) + TextSpan( + text: '${selectedIdentity!.name} ', + style: FromComposerMobileWidgetStyle.buttonTitleTextStyle, + ), + TextSpan( + text: '(${selectedIdentity!.email})', + style: selectedIdentity!.name!.isNotEmpty + ? FromComposerMobileWidgetStyle.buttonSubTitleTextStyle + : FromComposerMobileWidgetStyle.buttonTitleTextStyle + ) + ] + ), ), - ), - ) - else - const SizedBox.shrink(), - SvgPicture.asset(imagePaths.icDropDown), - ], + ) + else + const SizedBox.shrink(), + SvgPicture.asset(imagePaths.icDropDown), + ], + ), ), ), ), diff --git a/lib/features/composer/presentation/widgets/mobile/tablet_app_bar_composer_widget.dart b/lib/features/composer/presentation/widgets/mobile/tablet_app_bar_composer_widget.dart index 09686971cd..4bd6e3a377 100644 --- a/lib/features/composer/presentation/widgets/mobile/tablet_app_bar_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/mobile/tablet_app_bar_composer_widget.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/app_bar_composer_widget_style.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/title_composer_widget.dart'; @@ -53,22 +54,30 @@ class TabletAppBarComposerWidget extends StatelessWidget { children: [ if (isNetworkConnectionAvailable) ...[ - TMailButtonWidget.fromIcon( - icon: _imagePaths.icAttachFile, - iconColor: AppBarComposerWidgetStyle.iconColor, - backgroundColor: Colors.transparent, - iconSize: AppBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).attach_file, - onTapActionCallback: attachFileAction, + Semantics( + container: true, + identifier: ComposerKeyValues.pickAttachmentButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icAttachFile, + iconColor: AppBarComposerWidgetStyle.iconColor, + backgroundColor: Colors.transparent, + iconSize: AppBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).attach_file, + onTapActionCallback: attachFileAction, + ), ), const SizedBox(width: AppBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icInsertImage, - iconColor: AppBarComposerWidgetStyle.iconColor, - backgroundColor: Colors.transparent, - iconSize: AppBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).insertImage, - onTapActionCallback: insertImageAction, + Semantics( + container: true, + identifier: ComposerKeyValues.pickInlineImageButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icInsertImage, + iconColor: AppBarComposerWidgetStyle.iconColor, + backgroundColor: Colors.transparent, + iconSize: AppBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).insertImage, + onTapActionCallback: insertImageAction, + ), ), const SizedBox(width: AppBarComposerWidgetStyle.space), ], diff --git a/lib/features/composer/presentation/widgets/recipient_composer_widget.dart b/lib/features/composer/presentation/widgets/recipient_composer_widget.dart index 283946f7af..da17ba7912 100644 --- a/lib/features/composer/presentation/widgets/recipient_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/recipient_composer_widget.dart @@ -16,6 +16,7 @@ import 'package:model/email/prefix_email_address.dart'; import 'package:model/extensions/email_address_extension.dart'; import 'package:model/mailbox/expand_mode.dart'; import 'package:super_tag_editor/tag_editor.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart'; @@ -122,262 +123,290 @@ class _RecipientComposerWidgetState extends State { @override Widget build(BuildContext context) { - return Container( - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: RecipientComposerWidgetStyle.borderColor, - width: 1 + return Semantics( + identifier: switch (widget.prefix) { + PrefixEmailAddress.to => ComposerKeyValues.composerToField, + PrefixEmailAddress.cc => ComposerKeyValues.composerCcField, + PrefixEmailAddress.bcc => ComposerKeyValues.composerBccField, + _ => null, + }, + container: true, + child: Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: RecipientComposerWidgetStyle.borderColor, + width: 1 + ) ) - ) - ), - padding: widget.padding, - margin: widget.margin, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: RecipientComposerWidgetStyle.labelMargin, - child: Text( - '${widget.prefix.asName(context)}:', - key: Key('prefix_${widget.prefix.name}_recipient_composer_widget'), - style: RecipientComposerWidgetStyle.labelTextStyle + ), + padding: widget.padding, + margin: widget.margin, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: RecipientComposerWidgetStyle.labelMargin, + child: Text( + '${widget.prefix.asName(context)}:', + key: Key('prefix_${widget.prefix.name}_recipient_composer_widget'), + style: RecipientComposerWidgetStyle.labelTextStyle + ), ), - ), - const SizedBox(width: RecipientComposerWidgetStyle.space), - Expanded( - child: FocusScope( - child: Focus( - onFocusChange: (focus) => widget.onFocusEmailAddressChangeAction?.call(widget.prefix, focus), - onKeyEvent: PlatformInfo.isWeb ? _recipientInputOnKeyListener : null, - child: StatefulBuilder( - builder: (context, stateSetter) { - if (PlatformInfo.isWeb || widget.isTestingForWeb) { - return DragTarget( - builder: (context, candidateData, rejectedData) { - return TagEditor( - key: widget.keyTagEditor, - length: _collapsedListEmailAddress.length, - controller: widget.controller, - focusNode: widget.focusNode, - enableBorder: _isDragging, - focusNodeKeyboard: widget.focusNodeKeyboard, - borderRadius: RecipientComposerWidgetStyle.enableBorderRadius, - enableBorderColor: RecipientComposerWidgetStyle.enableBorderColor, - keyboardType: TextInputType.emailAddress, - textInputAction: TextInputAction.done, - debounceDuration: RecipientComposerWidgetStyle.suggestionDebounceDuration, - tagSpacing: RecipientComposerWidgetStyle.tagSpacing, - autofocus: widget.prefix != PrefixEmailAddress.to && _currentListEmailAddress.isEmpty, - minTextFieldWidth: RecipientComposerWidgetStyle.minTextFieldWidth, - resetTextOnSubmitted: true, - autoScrollToInput: false, - autoHideTextInputField: true, - cursorColor: RecipientComposerWidgetStyle.cursorColor, - suggestionsBoxElevation: RecipientComposerWidgetStyle.suggestionsBoxElevation, - suggestionsBoxBackgroundColor: RecipientComposerWidgetStyle.suggestionsBoxBackgroundColor, - suggestionsBoxRadius: RecipientComposerWidgetStyle.suggestionsBoxRadius, - suggestionsBoxMaxHeight: RecipientComposerWidgetStyle.suggestionsBoxMaxHeight, - suggestionBoxWidth: _getSuggestionBoxWidth(widget.maxWidth), - textStyle: RecipientComposerWidgetStyle.inputTextStyle, - onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), - onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), - onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), - onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), - onTapOutside: (_) {}, - onFocusTextInput: () { - if (_isCollapse) { - widget.onShowFullListEmailAddressAction?.call(widget.prefix); - } - }, - inputDecoration: const InputDecoration(border: InputBorder.none), - tagBuilder: (context, index) { - final currentEmailAddress = _currentListEmailAddress[index]; - final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; - - return RecipientTagItemWidget( - index: index, - imagePaths: widget.imagePaths, - prefix: widget.prefix, - currentEmailAddress: currentEmailAddress, - currentListEmailAddress: _currentListEmailAddress, - collapsedListEmailAddress: _collapsedListEmailAddress, - isLatestEmail: isLatestEmail, - isCollapsed: _isCollapse, - isLatestTagFocused: _lastTagFocused, - maxWidth: widget.maxWidth, - onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), - onShowFullAction: widget.onShowFullListEmailAddressAction, - ); - }, - onTagChanged: (value) => _handleOnTagChangeAction.call(value, stateSetter), - findSuggestions: _findSuggestions, - useDefaultHighlight: false, - suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { - return RecipientSuggestionItemWidget( - imagePaths: widget.imagePaths, - suggestionState: suggestionEmailAddress.state, - emailAddress: suggestionEmailAddress.emailAddress, - suggestionValid: suggestionValid, - highlight: highlight, - onSelectedAction: (emailAddress) { - stateSetter(() => _currentListEmailAddress.add(emailAddress)); - _updateListEmailAddressAction(); - tagEditorState.resetTextField(); - tagEditorState.closeSuggestionBox(); - }, - ); - }, - ); - }, - onAcceptWithDetails: (draggableEmailAddress) => _handleAcceptDraggableEmailAddressAction(draggableEmailAddress.data, stateSetter), - onLeave: (draggableEmailAddress) { - if (_isDragging) { - stateSetter(() => _isDragging = false); - } - }, - onMove: (details) { - if (!_isDragging) { - stateSetter(() => _isDragging = true); - } - }, - ); - } else { - return TagEditor( - key: widget.keyTagEditor, - length: _collapsedListEmailAddress.length, - controller: widget.controller, - focusNode: widget.focusNode, - focusNodeKeyboard: widget.focusNodeKeyboard, - keyboardType: TextInputType.emailAddress, - textInputAction: TextInputAction.done, - debounceDuration: RecipientComposerWidgetStyle.suggestionDebounceDuration, - tagSpacing: RecipientComposerWidgetStyle.tagSpacing, - minTextFieldWidth: RecipientComposerWidgetStyle.minTextFieldWidth, - resetTextOnSubmitted: true, - autoScrollToInput: false, - autoHideTextInputField: true, - cursorColor: RecipientComposerWidgetStyle.cursorColor, - suggestionsBoxElevation: RecipientComposerWidgetStyle.suggestionsBoxElevation, - suggestionsBoxBackgroundColor: RecipientComposerWidgetStyle.suggestionsBoxBackgroundColor, - suggestionsBoxRadius: RecipientComposerWidgetStyle.suggestionsBoxRadius, - suggestionsBoxMaxHeight: RecipientComposerWidgetStyle.suggestionsBoxMaxHeight, - suggestionBoxWidth: _getSuggestionBoxWidth(widget.maxWidth), - textStyle: RecipientComposerWidgetStyle.inputTextStyle, - onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), - onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), - onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), - onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), - onTapOutside: (_) {}, - onFocusTextInput: () { - if (_isCollapse) { - widget.onShowFullListEmailAddressAction?.call(widget.prefix); - } - }, - inputDecoration: const InputDecoration(border: InputBorder.none), - tagBuilder: (context, index) { - final currentEmailAddress = _currentListEmailAddress[index]; - final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; - - return RecipientTagItemWidget( - index: index, - imagePaths: widget.imagePaths, - prefix: widget.prefix, - currentEmailAddress: currentEmailAddress, - currentListEmailAddress: _currentListEmailAddress, - collapsedListEmailAddress: _collapsedListEmailAddress, - isLatestEmail: isLatestEmail, - isCollapsed: _isCollapse, - isLatestTagFocused: _lastTagFocused, - maxWidth: widget.maxWidth, - onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), - onShowFullAction: widget.onShowFullListEmailAddressAction, - ); - }, - onTagChanged: (value) => _handleOnTagChangeAction.call(value, stateSetter), - findSuggestions: _findSuggestions, - useDefaultHighlight: false, - suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { - return RecipientSuggestionItemWidget( - imagePaths: widget.imagePaths, - suggestionState: suggestionEmailAddress.state, - emailAddress: suggestionEmailAddress.emailAddress, - suggestionValid: suggestionValid, - highlight: highlight, - onSelectedAction: (emailAddress) { - stateSetter(() => _currentListEmailAddress.add(emailAddress)); - _updateListEmailAddressAction(); - tagEditorState.resetTextField(); - tagEditorState.closeSuggestionBox(); - }, - ); - }, - ); - } - }, + const SizedBox(width: RecipientComposerWidgetStyle.space), + Expanded( + child: FocusScope( + child: Focus( + onFocusChange: (focus) => widget.onFocusEmailAddressChangeAction?.call(widget.prefix, focus), + onKeyEvent: PlatformInfo.isWeb ? _recipientInputOnKeyListener : null, + child: StatefulBuilder( + builder: (context, stateSetter) { + if (PlatformInfo.isWeb || widget.isTestingForWeb) { + return DragTarget( + builder: (context, candidateData, rejectedData) { + return TagEditor( + key: widget.keyTagEditor, + length: _collapsedListEmailAddress.length, + controller: widget.controller, + focusNode: widget.focusNode, + enableBorder: _isDragging, + focusNodeKeyboard: widget.focusNodeKeyboard, + borderRadius: RecipientComposerWidgetStyle.enableBorderRadius, + enableBorderColor: RecipientComposerWidgetStyle.enableBorderColor, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.done, + debounceDuration: RecipientComposerWidgetStyle.suggestionDebounceDuration, + tagSpacing: RecipientComposerWidgetStyle.tagSpacing, + autofocus: widget.prefix != PrefixEmailAddress.to && _currentListEmailAddress.isEmpty, + minTextFieldWidth: RecipientComposerWidgetStyle.minTextFieldWidth, + resetTextOnSubmitted: true, + autoScrollToInput: false, + autoHideTextInputField: true, + cursorColor: RecipientComposerWidgetStyle.cursorColor, + suggestionsBoxElevation: RecipientComposerWidgetStyle.suggestionsBoxElevation, + suggestionsBoxBackgroundColor: RecipientComposerWidgetStyle.suggestionsBoxBackgroundColor, + suggestionsBoxRadius: RecipientComposerWidgetStyle.suggestionsBoxRadius, + suggestionsBoxMaxHeight: RecipientComposerWidgetStyle.suggestionsBoxMaxHeight, + suggestionBoxWidth: _getSuggestionBoxWidth(widget.maxWidth), + textStyle: RecipientComposerWidgetStyle.inputTextStyle, + onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), + onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), + onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), + onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), + onTapOutside: (_) {}, + onFocusTextInput: () { + if (_isCollapse) { + widget.onShowFullListEmailAddressAction?.call(widget.prefix); + } + }, + inputDecoration: const InputDecoration(border: InputBorder.none), + tagBuilder: (context, index) { + final currentEmailAddress = _currentListEmailAddress[index]; + final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; + + return RecipientTagItemWidget( + index: index, + imagePaths: widget.imagePaths, + prefix: widget.prefix, + currentEmailAddress: currentEmailAddress, + currentListEmailAddress: _currentListEmailAddress, + collapsedListEmailAddress: _collapsedListEmailAddress, + isLatestEmail: isLatestEmail, + isCollapsed: _isCollapse, + isLatestTagFocused: _lastTagFocused, + maxWidth: widget.maxWidth, + onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), + onShowFullAction: widget.onShowFullListEmailAddressAction, + ); + }, + onTagChanged: (value) => _handleOnTagChangeAction.call(value, stateSetter), + findSuggestions: _findSuggestions, + useDefaultHighlight: false, + suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { + return Semantics( + label: 'Composer:suggestion:${suggestionEmailAddress.emailAddress.email}', + child: RecipientSuggestionItemWidget( + imagePaths: widget.imagePaths, + suggestionState: suggestionEmailAddress.state, + emailAddress: suggestionEmailAddress.emailAddress, + suggestionValid: suggestionValid, + highlight: highlight, + onSelectedAction: (emailAddress) { + stateSetter(() => _currentListEmailAddress.add(emailAddress)); + _updateListEmailAddressAction(); + tagEditorState.resetTextField(); + tagEditorState.closeSuggestionBox(); + }, + ), + ); + }, + ); + }, + onAcceptWithDetails: (draggableEmailAddress) => _handleAcceptDraggableEmailAddressAction(draggableEmailAddress.data, stateSetter), + onLeave: (draggableEmailAddress) { + if (_isDragging) { + stateSetter(() => _isDragging = false); + } + }, + onMove: (details) { + if (!_isDragging) { + stateSetter(() => _isDragging = true); + } + }, + ); + } else { + return TagEditor( + key: widget.keyTagEditor, + length: _collapsedListEmailAddress.length, + controller: widget.controller, + focusNode: widget.focusNode, + focusNodeKeyboard: widget.focusNodeKeyboard, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.done, + debounceDuration: RecipientComposerWidgetStyle.suggestionDebounceDuration, + tagSpacing: RecipientComposerWidgetStyle.tagSpacing, + minTextFieldWidth: RecipientComposerWidgetStyle.minTextFieldWidth, + resetTextOnSubmitted: true, + autoScrollToInput: false, + autoHideTextInputField: true, + cursorColor: RecipientComposerWidgetStyle.cursorColor, + suggestionsBoxElevation: RecipientComposerWidgetStyle.suggestionsBoxElevation, + suggestionsBoxBackgroundColor: RecipientComposerWidgetStyle.suggestionsBoxBackgroundColor, + suggestionsBoxRadius: RecipientComposerWidgetStyle.suggestionsBoxRadius, + suggestionsBoxMaxHeight: RecipientComposerWidgetStyle.suggestionsBoxMaxHeight, + suggestionBoxWidth: _getSuggestionBoxWidth(widget.maxWidth), + textStyle: RecipientComposerWidgetStyle.inputTextStyle, + onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), + onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), + onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), + onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), + onTapOutside: (_) {}, + onFocusTextInput: () { + if (_isCollapse) { + widget.onShowFullListEmailAddressAction?.call(widget.prefix); + } + }, + inputDecoration: const InputDecoration(border: InputBorder.none), + tagBuilder: (context, index) { + final currentEmailAddress = _currentListEmailAddress[index]; + final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; + + return RecipientTagItemWidget( + index: index, + imagePaths: widget.imagePaths, + prefix: widget.prefix, + currentEmailAddress: currentEmailAddress, + currentListEmailAddress: _currentListEmailAddress, + collapsedListEmailAddress: _collapsedListEmailAddress, + isLatestEmail: isLatestEmail, + isCollapsed: _isCollapse, + isLatestTagFocused: _lastTagFocused, + maxWidth: widget.maxWidth, + onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), + onShowFullAction: widget.onShowFullListEmailAddressAction, + ); + }, + onTagChanged: (value) => _handleOnTagChangeAction.call(value, stateSetter), + findSuggestions: _findSuggestions, + useDefaultHighlight: false, + suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { + return RecipientSuggestionItemWidget( + imagePaths: widget.imagePaths, + suggestionState: suggestionEmailAddress.state, + emailAddress: suggestionEmailAddress.emailAddress, + suggestionValid: suggestionValid, + highlight: highlight, + onSelectedAction: (emailAddress) { + stateSetter(() => _currentListEmailAddress.add(emailAddress)); + _updateListEmailAddressAction(); + tagEditorState.resetTextField(); + tagEditorState.closeSuggestionBox(); + }, + ); + }, + ); + } + }, + ) ) ) - ) - ), - const SizedBox(width: RecipientComposerWidgetStyle.space), - if (widget.prefix == PrefixEmailAddress.to) - if (PlatformInfo.isWeb || widget.isTestingForWeb) - ...[ - if (widget.fromState == PrefixRecipientState.disabled) - TMailButtonWidget.fromText( - key: Key('prefix_${widget.prefix.name}_recipient_from_button'), - text: AppLocalizations.of(context).from_email_address_prefix, - textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, - backgroundColor: Colors.transparent, - padding: RecipientComposerWidgetStyle.prefixButtonPadding, - margin: RecipientComposerWidgetStyle.recipientMargin, - onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.from), - ), - if (widget.ccState == PrefixRecipientState.disabled) - TMailButtonWidget.fromText( - key: Key('prefix_${widget.prefix.name}_recipient_cc_button'), - text: AppLocalizations.of(context).cc_email_address_prefix, - textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, - backgroundColor: Colors.transparent, - padding: RecipientComposerWidgetStyle.prefixButtonPadding, - margin: RecipientComposerWidgetStyle.recipientMargin, - onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.cc), - ), - if (widget.bccState == PrefixRecipientState.disabled) - TMailButtonWidget.fromText( - key: Key('prefix_${widget.prefix.name}_recipient_bcc_button'), - text: AppLocalizations.of(context).bcc_email_address_prefix, - textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, + ), + const SizedBox(width: RecipientComposerWidgetStyle.space), + if (widget.prefix == PrefixEmailAddress.to) + if (PlatformInfo.isWeb || widget.isTestingForWeb) + ...[ + if (widget.fromState == PrefixRecipientState.disabled) + Semantics( + container: true, + identifier: ComposerKeyValues.openWebFromFieldButton, + child: TMailButtonWidget.fromText( + key: Key('prefix_${widget.prefix.name}_recipient_from_button'), + text: AppLocalizations.of(context).from_email_address_prefix, + textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, + backgroundColor: Colors.transparent, + padding: RecipientComposerWidgetStyle.prefixButtonPadding, + margin: RecipientComposerWidgetStyle.recipientMargin, + onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.from), + ), + ), + if (widget.ccState == PrefixRecipientState.disabled) + Semantics( + container: true, + identifier: ComposerKeyValues.openWebCcFieldButton, + child: TMailButtonWidget.fromText( + key: Key('prefix_${widget.prefix.name}_recipient_cc_button'), + text: AppLocalizations.of(context).cc_email_address_prefix, + textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, + backgroundColor: Colors.transparent, + padding: RecipientComposerWidgetStyle.prefixButtonPadding, + margin: RecipientComposerWidgetStyle.recipientMargin, + onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.cc), + ), + ), + if (widget.bccState == PrefixRecipientState.disabled) + Semantics( + container: true, + identifier: ComposerKeyValues.openWebBccFieldButton, + child: TMailButtonWidget.fromText( + key: Key('prefix_${widget.prefix.name}_recipient_bcc_button'), + text: AppLocalizations.of(context).bcc_email_address_prefix, + textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, + backgroundColor: Colors.transparent, + padding: RecipientComposerWidgetStyle.prefixButtonPadding, + margin: RecipientComposerWidgetStyle.recipientMargin, + onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.bcc), + ), + ), + ] + else if (PlatformInfo.isMobile) + Semantics( + container: true, + identifier: ComposerKeyValues.openMobileSecondaryFieldsButton, + child: TMailButtonWidget.fromIcon( + key: Key('prefix_${widget.prefix.name}_recipient_expand_button'), + icon: _isAllRecipientInputEnabled + ? widget.imagePaths.icChevronUp + : widget.imagePaths.icChevronDownOutline, backgroundColor: Colors.transparent, - padding: RecipientComposerWidgetStyle.prefixButtonPadding, - margin: RecipientComposerWidgetStyle.recipientMargin, - onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.bcc), + iconSize: 24, + padding: const EdgeInsets.all(5), + iconColor: AppColor.colorLabelComposer, + margin: RecipientComposerWidgetStyle.enableRecipientButtonMargin, + onTapActionCallback: () => widget.onEnableAllRecipientsInputAction?.call(_isAllRecipientInputEnabled), ), - ] - else if (PlatformInfo.isMobile) + ) + else if (PlatformInfo.isWeb || widget.isTestingForWeb) TMailButtonWidget.fromIcon( - key: Key('prefix_${widget.prefix.name}_recipient_expand_button'), - icon: _isAllRecipientInputEnabled - ? widget.imagePaths.icChevronUp - : widget.imagePaths.icChevronDownOutline, + icon: widget.imagePaths.icClose, backgroundColor: Colors.transparent, - iconSize: 24, - padding: const EdgeInsets.all(5), - iconColor: AppColor.colorLabelComposer, - margin: RecipientComposerWidgetStyle.enableRecipientButtonMargin, - onTapActionCallback: () => widget.onEnableAllRecipientsInputAction?.call(_isAllRecipientInputEnabled), + iconColor: RecipientComposerWidgetStyle.deleteRecipientFieldIconColor, + iconSize: RecipientComposerWidgetStyle.deleteRecipientFieldIconSize, + padding: RecipientComposerWidgetStyle.deleteRecipientFieldIconPadding, + margin: RecipientComposerWidgetStyle.recipientMargin, + onTapActionCallback: () => widget.onDeleteEmailAddressTypeAction?.call(widget.prefix), ) - else if (PlatformInfo.isWeb || widget.isTestingForWeb) - TMailButtonWidget.fromIcon( - icon: widget.imagePaths.icClose, - backgroundColor: Colors.transparent, - iconColor: RecipientComposerWidgetStyle.deleteRecipientFieldIconColor, - iconSize: RecipientComposerWidgetStyle.deleteRecipientFieldIconSize, - padding: RecipientComposerWidgetStyle.deleteRecipientFieldIconPadding, - margin: RecipientComposerWidgetStyle.recipientMargin, - onTapActionCallback: () => widget.onDeleteEmailAddressTypeAction?.call(widget.prefix), - ) - ] + ] + ), ), ); } diff --git a/lib/features/composer/presentation/widgets/subject_composer_widget.dart b/lib/features/composer/presentation/widgets/subject_composer_widget.dart index 7dfc0e2bc1..ab91b7a10b 100644 --- a/lib/features/composer/presentation/widgets/subject_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/subject_composer_widget.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/views/text/text_field_builder.dart'; import 'package:core/utils/direction_utils.dart'; import 'package:flutter/material.dart'; import 'package:languagetool_textfield/languagetool_textfield.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/subject_composer_widget_style.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -24,37 +25,41 @@ class SubjectComposerWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: SubjectComposerWidgetStyle.borderColor, - width: 1 - ) - ), - ), - margin: margin, - padding: padding, - child: Row( - children: [ - Text( - '${AppLocalizations.of(context).subject_email}:', - style: SubjectComposerWidgetStyle.labelTextStyle + return Semantics( + identifier: ComposerKeyValues.composerSubjectField, + container: true, + child: Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: SubjectComposerWidgetStyle.borderColor, + width: 1 + ) ), - const SizedBox(width:SubjectComposerWidgetStyle.space), - Expanded( - child: TextFieldBuilder( - cursorColor: SubjectComposerWidgetStyle.cursorColor, - focusNode: focusNode, - onTextChange: onTextChange, - maxLines: 1, - decoration: const InputDecoration(border: InputBorder.none), - textDirection: DirectionUtils.getDirectionByLanguage(context), - textStyle: SubjectComposerWidgetStyle.inputTextStyle, - languageToolController: textController, + ), + margin: margin, + padding: padding, + child: Row( + children: [ + Text( + '${AppLocalizations.of(context).subject_email}:', + style: SubjectComposerWidgetStyle.labelTextStyle + ), + const SizedBox(width:SubjectComposerWidgetStyle.space), + Expanded( + child: TextFieldBuilder( + cursorColor: SubjectComposerWidgetStyle.cursorColor, + focusNode: focusNode, + onTextChange: onTextChange, + maxLines: 1, + decoration: const InputDecoration(border: InputBorder.none), + textDirection: DirectionUtils.getDirectionByLanguage(context), + textStyle: SubjectComposerWidgetStyle.inputTextStyle, + languageToolController: textController, + ) ) - ) - ] + ] + ), ), ); } diff --git a/lib/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart b/lib/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart index 6687d43927..d1c1865a40 100644 --- a/lib/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/web/bottom_bar_composer_widget_style.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -44,104 +45,135 @@ class BottomBarComposerWidget extends StatelessWidget { color: BottomBarComposerWidgetStyle.backgroundColor, child: Row( children: [ - TMailButtonWidget.fromIcon( - icon: _imagePaths.icRichToolbar, - borderRadius: BottomBarComposerWidgetStyle.iconRadius, - padding: BottomBarComposerWidgetStyle.richTextIconPadding, - backgroundColor: isFormattingOptionsEnabled - ? BottomBarComposerWidgetStyle.selectedBackgroundColor - : Colors.transparent, - iconSize: BottomBarComposerWidgetStyle.richTextIconSize, - iconColor: isFormattingOptionsEnabled - ? BottomBarComposerWidgetStyle.selectedIconColor - : BottomBarComposerWidgetStyle.iconColor, - tooltipMessage: AppLocalizations.of(context).formattingOptions, - onTapActionCallback: openRichToolbarAction, - ), - const SizedBox(width: BottomBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icAttachFile, - iconColor: BottomBarComposerWidgetStyle.iconColor, - borderRadius: BottomBarComposerWidgetStyle.iconRadius, - backgroundColor: Colors.transparent, - padding: BottomBarComposerWidgetStyle.iconPadding, - iconSize: BottomBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).attach_file, - onTapActionCallback: attachFileAction, + Semantics( + container: true, + identifier: ComposerKeyValues.openTextFormattingMenuButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icRichToolbar, + borderRadius: BottomBarComposerWidgetStyle.iconRadius, + padding: BottomBarComposerWidgetStyle.richTextIconPadding, + backgroundColor: isFormattingOptionsEnabled + ? BottomBarComposerWidgetStyle.selectedBackgroundColor + : Colors.transparent, + iconSize: BottomBarComposerWidgetStyle.richTextIconSize, + iconColor: isFormattingOptionsEnabled + ? BottomBarComposerWidgetStyle.selectedIconColor + : BottomBarComposerWidgetStyle.iconColor, + tooltipMessage: AppLocalizations.of(context).formattingOptions, + onTapActionCallback: openRichToolbarAction, + ), ), const SizedBox(width: BottomBarComposerWidgetStyle.space), - AbsorbPointer( - absorbing: isCodeViewEnabled, + Semantics( + container: true, + identifier: ComposerKeyValues.pickAttachmentButton, child: TMailButtonWidget.fromIcon( - icon: _imagePaths.icInsertImage, + icon: _imagePaths.icAttachFile, iconColor: BottomBarComposerWidgetStyle.iconColor, borderRadius: BottomBarComposerWidgetStyle.iconRadius, backgroundColor: Colors.transparent, padding: BottomBarComposerWidgetStyle.iconPadding, iconSize: BottomBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).insertImage, - onTapActionCallback: insertImageAction, + tooltipMessage: AppLocalizations.of(context).attach_file, + onTapActionCallback: attachFileAction, ), ), const SizedBox(width: BottomBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icStyleCodeView, - iconColor: isCodeViewEnabled - ? BottomBarComposerWidgetStyle.selectedIconColor - : BottomBarComposerWidgetStyle.iconColor, - borderRadius: BottomBarComposerWidgetStyle.iconRadius, - backgroundColor: isCodeViewEnabled - ? BottomBarComposerWidgetStyle.selectedBackgroundColor - : Colors.transparent, - padding: BottomBarComposerWidgetStyle.iconPadding, - iconSize: BottomBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).embedCode, - onTapActionCallback: showCodeViewAction, + AbsorbPointer( + absorbing: isCodeViewEnabled, + child: Semantics( + container: true, + identifier: ComposerKeyValues.pickInlineImageButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icInsertImage, + iconColor: BottomBarComposerWidgetStyle.iconColor, + borderRadius: BottomBarComposerWidgetStyle.iconRadius, + backgroundColor: Colors.transparent, + padding: BottomBarComposerWidgetStyle.iconPadding, + iconSize: BottomBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).insertImage, + onTapActionCallback: insertImageAction, + ), + ), + ), + const SizedBox(width: BottomBarComposerWidgetStyle.space), + Semantics( + container: true, + identifier: ComposerKeyValues.toggleCodeViewButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icStyleCodeView, + iconColor: isCodeViewEnabled + ? BottomBarComposerWidgetStyle.selectedIconColor + : BottomBarComposerWidgetStyle.iconColor, + borderRadius: BottomBarComposerWidgetStyle.iconRadius, + backgroundColor: isCodeViewEnabled + ? BottomBarComposerWidgetStyle.selectedBackgroundColor + : Colors.transparent, + padding: BottomBarComposerWidgetStyle.iconPadding, + iconSize: BottomBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).embedCode, + onTapActionCallback: showCodeViewAction, + ), ), const Spacer(), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icDeleteMailbox, - borderRadius: BottomBarComposerWidgetStyle.iconRadius, - padding: BottomBarComposerWidgetStyle.iconPadding, - iconSize: BottomBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).delete, - onTapActionCallback: deleteComposerAction, + Semantics( + container: true, + identifier: ComposerKeyValues.deleteComposedEmailButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icDeleteMailbox, + borderRadius: BottomBarComposerWidgetStyle.iconRadius, + padding: BottomBarComposerWidgetStyle.iconPadding, + iconSize: BottomBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).delete, + onTapActionCallback: deleteComposerAction, + ), ), const SizedBox(width: BottomBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icReadReceipt, - borderRadius: BottomBarComposerWidgetStyle.iconRadius, - padding: BottomBarComposerWidgetStyle.iconPadding, - iconSize: BottomBarComposerWidgetStyle.iconSize, - iconColor: hasReadReceipt - ? BottomBarComposerWidgetStyle.selectedIconColor - : BottomBarComposerWidgetStyle.iconColor, - tooltipMessage: hasReadReceipt - ? AppLocalizations.of(context).turnOffRequestReadReceipt - : AppLocalizations.of(context).turnOnRequestReadReceipt, - onTapActionCallback: requestReadReceiptAction, + Semantics( + container: true, + identifier: ComposerKeyValues.toggleReadRecipientsButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icReadReceipt, + borderRadius: BottomBarComposerWidgetStyle.iconRadius, + padding: BottomBarComposerWidgetStyle.iconPadding, + iconSize: BottomBarComposerWidgetStyle.iconSize, + iconColor: hasReadReceipt + ? BottomBarComposerWidgetStyle.selectedIconColor + : BottomBarComposerWidgetStyle.iconColor, + tooltipMessage: hasReadReceipt + ? AppLocalizations.of(context).turnOffRequestReadReceipt + : AppLocalizations.of(context).turnOnRequestReadReceipt, + onTapActionCallback: requestReadReceiptAction, + ), ), const SizedBox(width: BottomBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icSaveToDraft, - borderRadius: BottomBarComposerWidgetStyle.iconRadius, - padding: BottomBarComposerWidgetStyle.iconPadding, - iconSize: BottomBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).saveAsDraft, - onTapActionCallback: saveToDraftAction, + Semantics( + container: true, + identifier: ComposerKeyValues.saveAsDraftButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icSaveToDraft, + borderRadius: BottomBarComposerWidgetStyle.iconRadius, + padding: BottomBarComposerWidgetStyle.iconPadding, + iconSize: BottomBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).saveAsDraft, + onTapActionCallback: saveToDraftAction, + ), ), const SizedBox(width: BottomBarComposerWidgetStyle.sendButtonSpace), - TMailButtonWidget( - text: AppLocalizations.of(context).send, - icon: _imagePaths.icSend, - iconAlignment: TextDirection.rtl, - padding: BottomBarComposerWidgetStyle.sendButtonPadding, - iconSize: BottomBarComposerWidgetStyle.iconSize, - iconSpace: BottomBarComposerWidgetStyle.sendButtonIconSpace, - textStyle: BottomBarComposerWidgetStyle.sendButtonTextStyle, - backgroundColor: BottomBarComposerWidgetStyle.sendButtonBackgroundColor, - borderRadius: BottomBarComposerWidgetStyle.sendButtonRadius, - onTapActionCallback: sendMessageAction, + Semantics( + identifier: ComposerKeyValues.composerSendButton, + child: TMailButtonWidget( + text: AppLocalizations.of(context).send, + icon: _imagePaths.icSend, + iconAlignment: TextDirection.rtl, + padding: BottomBarComposerWidgetStyle.sendButtonPadding, + iconSize: BottomBarComposerWidgetStyle.iconSize, + iconSpace: BottomBarComposerWidgetStyle.sendButtonIconSpace, + textStyle: BottomBarComposerWidgetStyle.sendButtonTextStyle, + backgroundColor: BottomBarComposerWidgetStyle.sendButtonBackgroundColor, + borderRadius: BottomBarComposerWidgetStyle.sendButtonRadius, + onTapActionCallback: sendMessageAction, + ), ) ] ), diff --git a/lib/features/composer/presentation/widgets/web/dropdown_menu_font_size_widget.dart b/lib/features/composer/presentation/widgets/web/dropdown_menu_font_size_widget.dart index d3446bee66..8b5254c79f 100644 --- a/lib/features/composer/presentation/widgets/web/dropdown_menu_font_size_widget.dart +++ b/lib/features/composer/presentation/widgets/web/dropdown_menu_font_size_widget.dart @@ -1,6 +1,7 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/web/dropdown_menu_font_size_widget_style.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/web/dropdown_button_font_size_widget.dart'; @@ -25,9 +26,13 @@ class DropdownMenuFontSizeWidget extends StatelessWidget { items: RichTextWebController.fontSizeList.map((value) { return DropdownMenuItem( value: value, - child: ItemMenuFontSizeWidget( - value: value, - selectedValue: selectedFontSize + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextFontSizeOption, + child: ItemMenuFontSizeWidget( + value: value, + selectedValue: selectedFontSize + ), ) ); }).toList(), diff --git a/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart b/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart index 4dbec500b4..fd83f93ff2 100644 --- a/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart +++ b/lib/features/composer/presentation/widgets/web/from_composer_drop_down_widget.dart @@ -8,6 +8,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; import 'package:model/model.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/web/from_composer_drop_down_widget_style.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -63,123 +64,131 @@ class FromComposerDropDownWidget extends StatelessWidget { padding: FromComposerDropDownWidgetStyle.dropdownButtonPadding, child: DropdownButtonHideUnderline( child: PointerInterceptor( - child: DropdownButton2( - key: dropdownKey, - isExpanded: false, - items: items.map((item) => DropdownMenuItem( - value: item, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - padding: FromComposerDropDownWidgetStyle.dropdownItemPadding, - child: Row( - children: [ - Container( - width: FromComposerDropDownWidgetStyle.avatarSize, - height: FromComposerDropDownWidgetStyle.avatarSize, - alignment: Alignment.center, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.avatarColor, - border: Border.all( - color: AppColor.colorShadowBgContentEmail, - width: FromComposerDropDownWidgetStyle.avatarBorderWidth + child: Semantics( + container: true, + identifier: ComposerKeyValues.openFromEmailSelector, + child: DropdownButton2( + key: dropdownKey, + isExpanded: false, + items: items.map((item) => DropdownMenuItem( + value: item, + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.fromEmailSelectorOption, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + padding: FromComposerDropDownWidgetStyle.dropdownItemPadding, + child: Row( + children: [ + Container( + width: FromComposerDropDownWidgetStyle.avatarSize, + height: FromComposerDropDownWidgetStyle.avatarSize, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.avatarColor, + border: Border.all( + color: AppColor.colorShadowBgContentEmail, + width: FromComposerDropDownWidgetStyle.avatarBorderWidth + ) + ), + child: Text( + item.name!.isNotEmpty + ? item.name!.firstLetterToUpperCase + : item.email!.firstLetterToUpperCase, + style: FromComposerDropDownWidgetStyle.avatarTextStyle, + ), + ), + const SizedBox(width: FromComposerDropDownWidgetStyle.dropdownItemSpace), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (item.name!.isNotEmpty) + Text( + item.name!, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: FromComposerDropDownWidgetStyle.dropdownItemTitleTextStyle, + ), + if (item.email!.isNotEmpty) + Text( + item.email!, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: FromComposerDropDownWidgetStyle.dropdownItemSubTitleTextStyle, + ) + ], + ), ) - ), - child: Text( - item.name!.isNotEmpty - ? item.name!.firstLetterToUpperCase - : item.email!.firstLetterToUpperCase, - style: FromComposerDropDownWidgetStyle.avatarTextStyle, - ), + ], ), - const SizedBox(width: FromComposerDropDownWidgetStyle.dropdownItemSpace), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (item.name!.isNotEmpty) - Text( - item.name!, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - style: FromComposerDropDownWidgetStyle.dropdownItemTitleTextStyle, - ), - if (item.email!.isNotEmpty) - Text( - item.email!, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - style: FromComposerDropDownWidgetStyle.dropdownItemSubTitleTextStyle, - ) - ], - ), - ) - ], + ), ), ), - ), - )).toList(), - value: itemSelected, - buttonStyleData: FromComposerDropDownWidgetStyle.buttonStyleData, - dropdownSearchData: DropdownSearchData( - searchInnerWidget: Container( - padding: FromComposerDropDownWidgetStyle.dropdownTopBarPadding, - child: Text( - AppLocalizations.of(context).yourIdentities, - style: FromComposerDropDownWidgetStyle.dropdownTitleTextStyle, + )).toList(), + value: itemSelected, + buttonStyleData: FromComposerDropDownWidgetStyle.buttonStyleData, + dropdownSearchData: DropdownSearchData( + searchInnerWidget: Container( + padding: FromComposerDropDownWidgetStyle.dropdownTopBarPadding, + child: Text( + AppLocalizations.of(context).yourIdentities, + style: FromComposerDropDownWidgetStyle.dropdownTitleTextStyle, + ), ), + searchInnerWidgetHeight: FromComposerDropDownWidgetStyle.dropdownTopBarHeight, ), - searchInnerWidgetHeight: FromComposerDropDownWidgetStyle.dropdownTopBarHeight, - ), - dropdownStyleData: FromComposerDropDownWidgetStyle.dropdownStyleData, - iconStyleData: IconStyleData( - icon: SvgPicture.asset(imagePaths.icDropDown), - ), - menuItemStyleData: FromComposerDropDownWidgetStyle.menuIemStyleData, - customButton: Tooltip( - message: itemSelected != null ? itemSelected!.email : '', - child: Container( - height: FromComposerDropDownWidgetStyle.buttonHeight, - width: FromComposerDropDownWidgetStyle.buttonWidth, - padding: FromComposerDropDownWidgetStyle.buttonPadding, - decoration: FromComposerDropDownWidgetStyle.buttonDecoration, - child: Row( - children: [ - if (itemSelected != null) - Expanded( - child: RichText( - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - text: TextSpan( - children: [ - if (itemSelected!.name!.isNotEmpty) + dropdownStyleData: FromComposerDropDownWidgetStyle.dropdownStyleData, + iconStyleData: IconStyleData( + icon: SvgPicture.asset(imagePaths.icDropDown), + ), + menuItemStyleData: FromComposerDropDownWidgetStyle.menuIemStyleData, + customButton: Tooltip( + message: itemSelected != null ? itemSelected!.email : '', + child: Container( + height: FromComposerDropDownWidgetStyle.buttonHeight, + width: FromComposerDropDownWidgetStyle.buttonWidth, + padding: FromComposerDropDownWidgetStyle.buttonPadding, + decoration: FromComposerDropDownWidgetStyle.buttonDecoration, + child: Row( + children: [ + if (itemSelected != null) + Expanded( + child: RichText( + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + text: TextSpan( + children: [ + if (itemSelected!.name!.isNotEmpty) + TextSpan( + text: '${itemSelected!.name} ', + style: FromComposerDropDownWidgetStyle.dropdownButtonTitleTextStyle, + ), TextSpan( - text: '${itemSelected!.name} ', - style: FromComposerDropDownWidgetStyle.dropdownButtonTitleTextStyle, - ), - TextSpan( - text: '(${itemSelected!.email})', - style: itemSelected!.name!.isNotEmpty - ? FromComposerDropDownWidgetStyle.dropdownButtonSubTitleTextStyle - : FromComposerDropDownWidgetStyle.dropdownButtonTitleTextStyle - ) - ] - ), + text: '(${itemSelected!.email})', + style: itemSelected!.name!.isNotEmpty + ? FromComposerDropDownWidgetStyle.dropdownButtonSubTitleTextStyle + : FromComposerDropDownWidgetStyle.dropdownButtonTitleTextStyle + ) + ] + ), + ) ) - ) - else - const SizedBox.shrink(), - SvgPicture.asset(imagePaths.icDropDown) - ], + else + const SizedBox.shrink(), + SvgPicture.asset(imagePaths.icDropDown) + ], + ), ), ), + onChanged: onChangeIdentity, ), - onChanged: onChangeIdentity, ), ), ), diff --git a/lib/features/composer/presentation/widgets/web/item_menu_font_size_widget.dart b/lib/features/composer/presentation/widgets/web/item_menu_font_size_widget.dart index 576bea5e8d..3effc35f9a 100644 --- a/lib/features/composer/presentation/widgets/web/item_menu_font_size_widget.dart +++ b/lib/features/composer/presentation/widgets/web/item_menu_font_size_widget.dart @@ -21,26 +21,29 @@ class ItemMenuFontSizeWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return PointerInterceptor( - child: Container( - alignment: Alignment.center, - color: Colors.transparent, - child: Stack( + return Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( alignment: Alignment.center, - children: [ - Text( - '$value', - style: ItemMenuFontSizeWidgetStyle.labelTextStyle, - ), - if (value == selectedValue) - Align( - alignment: AlignmentDirectional.centerStart, - child: Padding( - padding: ItemMenuFontSizeWidgetStyle.selectIconPadding, - child: SvgPicture.asset(_imagePaths.icSelectedSB), + color: Colors.transparent, + child: Stack( + alignment: Alignment.center, + children: [ + Text( + '$value', + style: ItemMenuFontSizeWidgetStyle.labelTextStyle, + ), + if (value == selectedValue) + Align( + alignment: AlignmentDirectional.centerStart, + child: Padding( + padding: ItemMenuFontSizeWidgetStyle.selectIconPadding, + child: SvgPicture.asset(_imagePaths.icSelectedSB), + ) ) - ) - ], + ], + ), ), ), ); diff --git a/lib/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart b/lib/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart index 5c7b0790e3..5df362c250 100644 --- a/lib/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/mobile_app_bar_composer_widget_style.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -51,57 +52,77 @@ class MobileResponsiveAppBarComposerWidget extends StatelessWidget { onTapActionCallback: onCloseViewAction ), const Spacer(), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icRichToolbar, - padding: MobileAppBarComposerWidgetStyle.richTextIconPadding, - backgroundColor: isFormattingOptionsEnabled - ? MobileAppBarComposerWidgetStyle.selectedBackgroundColor - : Colors.transparent, - iconSize: MobileAppBarComposerWidgetStyle.richTextIconSize, - iconColor: isFormattingOptionsEnabled - ? MobileAppBarComposerWidgetStyle.selectedIconColor - : MobileAppBarComposerWidgetStyle.iconColor, - tooltipMessage: AppLocalizations.of(context).formattingOptions, - onTapActionCallback: openRichToolbarAction, - ), - const SizedBox(width: MobileAppBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icAttachFile, - iconColor: MobileAppBarComposerWidgetStyle.iconColor, - backgroundColor: Colors.transparent, - iconSize: MobileAppBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).attach_file, - onTapActionCallback: attachFileAction, + Semantics( + container: true, + identifier: ComposerKeyValues.openTextFormattingMenuButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icRichToolbar, + padding: MobileAppBarComposerWidgetStyle.richTextIconPadding, + backgroundColor: isFormattingOptionsEnabled + ? MobileAppBarComposerWidgetStyle.selectedBackgroundColor + : Colors.transparent, + iconSize: MobileAppBarComposerWidgetStyle.richTextIconSize, + iconColor: isFormattingOptionsEnabled + ? MobileAppBarComposerWidgetStyle.selectedIconColor + : MobileAppBarComposerWidgetStyle.iconColor, + tooltipMessage: AppLocalizations.of(context).formattingOptions, + onTapActionCallback: openRichToolbarAction, + ), ), const SizedBox(width: MobileAppBarComposerWidgetStyle.space), - if (!isCodeViewEnabled) - TMailButtonWidget.fromIcon( - icon: _imagePaths.icInsertImage, + Semantics( + container: true, + identifier: ComposerKeyValues.pickAttachmentButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icAttachFile, iconColor: MobileAppBarComposerWidgetStyle.iconColor, backgroundColor: Colors.transparent, iconSize: MobileAppBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).insertImage, - onTapActionCallback: insertImageAction, + tooltipMessage: AppLocalizations.of(context).attach_file, + onTapActionCallback: attachFileAction, ), + ), const SizedBox(width: MobileAppBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: isSendButtonEnabled - ? _imagePaths.icSendMobile - : _imagePaths.icSendDisable, - backgroundColor: Colors.transparent, - padding: MobileAppBarComposerWidgetStyle.iconPadding, - iconSize: MobileAppBarComposerWidgetStyle.sendButtonIconSize, - tooltipMessage: AppLocalizations.of(context).send, - onTapActionCallback: sendMessageAction, + if (!isCodeViewEnabled) + Semantics( + container: true, + identifier: ComposerKeyValues.pickInlineImageButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icInsertImage, + iconColor: MobileAppBarComposerWidgetStyle.iconColor, + backgroundColor: Colors.transparent, + iconSize: MobileAppBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).insertImage, + onTapActionCallback: insertImageAction, + ), + ), + const SizedBox(width: MobileAppBarComposerWidgetStyle.space), + Semantics( + identifier: ComposerKeyValues.composerSendButton, + container: true, + child: TMailButtonWidget.fromIcon( + icon: isSendButtonEnabled + ? _imagePaths.icSendMobile + : _imagePaths.icSendDisable, + backgroundColor: Colors.transparent, + padding: MobileAppBarComposerWidgetStyle.iconPadding, + iconSize: MobileAppBarComposerWidgetStyle.sendButtonIconSize, + tooltipMessage: AppLocalizations.of(context).send, + onTapActionCallback: sendMessageAction, + ), ), const SizedBox(width: MobileAppBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icMore, - iconColor: MobileAppBarComposerWidgetStyle.iconColor, - backgroundColor: Colors.transparent, - iconSize: MobileAppBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).more, - onTapActionAtPositionCallback: openContextMenuAction, + Semantics( + container: true, + identifier: ComposerKeyValues.openMobileMoreOptionsButton, + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icMore, + iconColor: MobileAppBarComposerWidgetStyle.iconColor, + backgroundColor: Colors.transparent, + iconSize: MobileAppBarComposerWidgetStyle.iconSize, + tooltipMessage: AppLocalizations.of(context).more, + onTapActionAtPositionCallback: openContextMenuAction, + ), ), ], ), diff --git a/lib/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart b/lib/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart index 8e98cb961a..040af8e6de 100644 --- a/lib/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart +++ b/lib/features/composer/presentation/widgets/web/toolbar_rich_text_builder.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/base/widget/drop_down_button_widget.dart'; import 'package:tmail_ui_user/features/base/widget/popup_menu_overlay_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart'; @@ -56,92 +57,114 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin ...extendedOption!, AbsorbPointer( absorbing: codeViewEnabled, - child: DropDownMenuHeaderStyleWidget( - icon: buildWrapIconStyleText( - isSelected: richTextWebController.isMenuHeaderStyleOpen, - icon: SvgPicture.asset( - RichTextStyleType.headerStyle.getIcon(_imagePaths), - colorFilter: AppColor.colorDefaultRichTextButton.withOpacity(opacity).asFilter(), - fit: BoxFit.fill + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextHeaderStyleButton, + child: DropDownMenuHeaderStyleWidget( + icon: buildWrapIconStyleText( + isSelected: richTextWebController.isMenuHeaderStyleOpen, + icon: SvgPicture.asset( + RichTextStyleType.headerStyle.getIcon(_imagePaths), + colorFilter: AppColor.colorDefaultRichTextButton.withOpacity(opacity).asFilter(), + fit: BoxFit.fill + ), + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), + tooltip: RichTextStyleType.headerStyle.getTooltipButton(context) ), - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), - tooltip: RichTextStyleType.headerStyle.getTooltipButton(context) + items: HeaderStyleType.values, + onMenuStateChange: (isOpen) { + final newStatus = isOpen + ? DropdownMenuFontStatus.open + : DropdownMenuFontStatus.closed; + richTextWebController.menuHeaderStyleStatus.value = newStatus; + }, + onChanged: richTextWebController.applyHeaderStyle ), - items: HeaderStyleType.values, - onMenuStateChange: (isOpen) { - final newStatus = isOpen - ? DropdownMenuFontStatus.open - : DropdownMenuFontStatus.closed; - richTextWebController.menuHeaderStyleStatus.value = newStatus; - }, - onChanged: richTextWebController.applyHeaderStyle ), ), AbsorbPointer( absorbing: codeViewEnabled, - child: DropdownMenuFontSizeWidget( - onChanged: richTextWebController.applyNewFontSize, - selectedFontSize: richTextWebController.selectedFontSize.value + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextFontSizeButton, + child: DropdownMenuFontSizeWidget( + onChanged: richTextWebController.applyNewFontSize, + selectedFontSize: richTextWebController.selectedFontSize.value + ), ), ), AbsorbPointer( absorbing: codeViewEnabled, child: SizedBox( width: 130, - child: DropDownButtonWidget( - items: FontNameType.values, - itemSelected: richTextWebController.selectedFontName.value, - onChanged: (newFont) => richTextWebController.applyNewFontStyle(newFont), - onMenuStateChange: (isOpen) { - final newStatus = isOpen - ? DropdownMenuFontStatus.open - : DropdownMenuFontStatus.closed; - richTextWebController.menuFontStatus.value = newStatus; - }, - heightItem: 40, - sizeIconChecked: 16, - radiusButton: 8, - opacity: opacity, - dropdownWidth: 200, - colorButton: richTextWebController.isMenuFontOpen - ? AppColor.colorBackgroundWrapIconStyleCode - : Colors.white, - iconArrowDown: SvgPicture.asset(_imagePaths.icStyleArrowDown), - tooltip: RichTextStyleType.fontName.getTooltipButton(context), - supportSelectionIcon: true + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextFontNameButton, + child: DropDownButtonWidget( + items: FontNameType.values, + semanticIdentifier: ComposerKeyValues.richtextFontNameOption, + itemSelected: richTextWebController.selectedFontName.value, + onChanged: (newFont) => richTextWebController.applyNewFontStyle(newFont), + onMenuStateChange: (isOpen) { + final newStatus = isOpen + ? DropdownMenuFontStatus.open + : DropdownMenuFontStatus.closed; + richTextWebController.menuFontStatus.value = newStatus; + }, + heightItem: 40, + sizeIconChecked: 16, + radiusButton: 8, + opacity: opacity, + dropdownWidth: 200, + colorButton: richTextWebController.isMenuFontOpen + ? AppColor.colorBackgroundWrapIconStyleCode + : Colors.white, + iconArrowDown: SvgPicture.asset(_imagePaths.icStyleArrowDown), + tooltip: RichTextStyleType.fontName.getTooltipButton(context), + supportSelectionIcon: true + ), ) ), ), AbsorbPointer( absorbing: codeViewEnabled, - child: buildWrapIconStyleText( - icon: buildIconWithTooltip( - path: RichTextStyleType.textColor.getIcon(_imagePaths), - color: richTextWebController.selectedTextColor.value, - tooltip: RichTextStyleType.textColor.getTooltipButton(context), - opacity: opacity + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextTextColorButton, + child: buildWrapIconStyleText( + icon: buildIconWithTooltip( + path: RichTextStyleType.textColor.getIcon(_imagePaths), + color: richTextWebController.selectedTextColor.value, + tooltip: RichTextStyleType.textColor.getTooltipButton(context), + opacity: opacity, + excludeFromSemantics: true + ), + onTap: () => richTextWebController.applyRichTextStyle( + context, + RichTextStyleType.textColor + ) ), - onTap: () => richTextWebController.applyRichTextStyle( - context, - RichTextStyleType.textColor - ) ), ), AbsorbPointer( absorbing: codeViewEnabled, - child: buildWrapIconStyleText( - padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 7), - spacing: 3, - icon: buildIconColorBackgroundText( - iconData: RichTextStyleType.textBackgroundColor.getIconData(), - colorSelected: richTextWebController.selectedTextBackgroundColor.value, - tooltip: RichTextStyleType.textBackgroundColor.getTooltipButton(context), - opacity: opacity + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextBackgroundColorButton, + child: buildWrapIconStyleText( + padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 7), + spacing: 3, + icon: buildIconColorBackgroundText( + iconData: RichTextStyleType.textBackgroundColor.getIconData(), + colorSelected: richTextWebController.selectedTextBackgroundColor.value, + tooltip: RichTextStyleType.textBackgroundColor.getTooltipButton(context), + opacity: opacity + ), + onTap: () => richTextWebController.applyRichTextStyle( + context, + RichTextStyleType.textBackgroundColor + ) ), - onTap: () => richTextWebController.applyRichTextStyle( - context, - RichTextStyleType.textBackgroundColor - ) ), ), buildWrapIconStyleText( @@ -150,101 +173,127 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin icon: Wrap(children: [ AbsorbPointer( absorbing: codeViewEnabled, - child: buildIconStyleText( - path: RichTextStyleType.bold.getIcon(_imagePaths), - isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.bold), - tooltip: RichTextStyleType.bold.getTooltipButton(context), - opacity: opacity, - onTap: () => richTextWebController.applyRichTextStyle( - context, - RichTextStyleType.bold - ) + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextBoldToggle, + child: buildIconStyleText( + path: RichTextStyleType.bold.getIcon(_imagePaths), + isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.bold), + tooltip: RichTextStyleType.bold.getTooltipButton(context), + opacity: opacity, + onTap: () => richTextWebController.applyRichTextStyle( + context, + RichTextStyleType.bold + ) + ), ), ), AbsorbPointer( absorbing: codeViewEnabled, - child: buildIconStyleText( - path: RichTextStyleType.italic.getIcon(_imagePaths), - isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.italic), - tooltip: RichTextStyleType.italic.getTooltipButton(context), - opacity: opacity, - onTap: () => richTextWebController.applyRichTextStyle( - context, - RichTextStyleType.italic - ) + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextItalicToggle, + child: buildIconStyleText( + path: RichTextStyleType.italic.getIcon(_imagePaths), + isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.italic), + tooltip: RichTextStyleType.italic.getTooltipButton(context), + opacity: opacity, + onTap: () => richTextWebController.applyRichTextStyle( + context, + RichTextStyleType.italic + ) + ), ), ), AbsorbPointer( absorbing: codeViewEnabled, - child: buildIconStyleText( - path: RichTextStyleType.underline.getIcon(_imagePaths), - isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.underline), - tooltip: RichTextStyleType.underline.getTooltipButton(context), - opacity: opacity, - onTap: () => richTextWebController.applyRichTextStyle( - context, - RichTextStyleType.underline - ) + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextUnderlineToggle, + child: buildIconStyleText( + path: RichTextStyleType.underline.getIcon(_imagePaths), + isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.underline), + tooltip: RichTextStyleType.underline.getTooltipButton(context), + opacity: opacity, + onTap: () => richTextWebController.applyRichTextStyle( + context, + RichTextStyleType.underline + ) + ), ), ), AbsorbPointer( absorbing: codeViewEnabled, - child: buildIconStyleText( - path: RichTextStyleType.strikeThrough.getIcon(_imagePaths), - isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.strikeThrough), - tooltip: RichTextStyleType.strikeThrough.getTooltipButton(context), - opacity: opacity, - onTap: () => richTextWebController.applyRichTextStyle( - context, - RichTextStyleType.strikeThrough - ) + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextStrikethroughToggle, + child: buildIconStyleText( + path: RichTextStyleType.strikeThrough.getIcon(_imagePaths), + isSelected: richTextWebController.isTextStyleTypeSelected(RichTextStyleType.strikeThrough), + tooltip: RichTextStyleType.strikeThrough.getTooltipButton(context), + opacity: opacity, + onTap: () => richTextWebController.applyRichTextStyle( + context, + RichTextStyleType.strikeThrough + ) + ), ), ) ]) ), AbsorbPointer( absorbing: codeViewEnabled, - child: PopupMenuOverlayWidget( - controller: richTextWebController.menuParagraphController, - listButtonAction: ParagraphType.values - .map((paragraph) => paragraph.buildButtonWidget( - context, - _imagePaths, - (paragraph) => richTextWebController.applyParagraphType(paragraph))) - .toList(), - iconButton: buildWrapIconStyleText( - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), - spacing: 3, - isSelected: richTextWebController.focusMenuParagraph.value, - icon: buildIconWithTooltip( - path: richTextWebController.selectedParagraph.value.getIcon(_imagePaths), - color: AppColor.colorDefaultRichTextButton, - opacity: opacity, - tooltip: RichTextStyleType.paragraph.getTooltipButton(context) - ) + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextAlignParagraphButton, + child: PopupMenuOverlayWidget( + controller: richTextWebController.menuParagraphController, + listButtonAction: ParagraphType.values + .map((paragraph) => paragraph.buildButtonWidget( + context, + _imagePaths, + (paragraph) => richTextWebController.applyParagraphType(paragraph))) + .toList(), + iconButton: buildWrapIconStyleText( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), + spacing: 3, + isSelected: richTextWebController.focusMenuParagraph.value, + icon: buildIconWithTooltip( + path: richTextWebController.selectedParagraph.value.getIcon(_imagePaths), + color: AppColor.colorDefaultRichTextButton, + opacity: opacity, + excludeFromSemantics: true, + tooltip: RichTextStyleType.paragraph.getTooltipButton(context) + ) + ), ), ), ), AbsorbPointer( absorbing: codeViewEnabled, - child: PopupMenuOverlayWidget( - controller: richTextWebController.menuOrderListController, - listButtonAction: OrderListType.values - .map((orderType) => orderType.buildButtonWidget( - context, - _imagePaths, - (orderType) => richTextWebController.applyOrderListType(orderType))) - .toList(), - iconButton: buildWrapIconStyleText( - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), - spacing: 3, - isSelected: richTextWebController.focusMenuOrderList.value, - icon: buildIconWithTooltip( - path: richTextWebController.selectedOrderList.value.getIcon(_imagePaths), - color: AppColor.colorDefaultRichTextButton, - opacity: opacity, - tooltip: RichTextStyleType.orderList.getTooltipButton(context) - ) + child: Semantics( + excludeSemantics: true, + identifier: ComposerKeyValues.richtextListStyleButton, + child: PopupMenuOverlayWidget( + controller: richTextWebController.menuOrderListController, + listButtonAction: OrderListType.values + .map((orderType) => orderType.buildButtonWidget( + context, + _imagePaths, + (orderType) => richTextWebController.applyOrderListType(orderType))) + .toList(), + iconButton: buildWrapIconStyleText( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), + spacing: 3, + isSelected: richTextWebController.focusMenuOrderList.value, + icon: buildIconWithTooltip( + path: richTextWebController.selectedOrderList.value.getIcon(_imagePaths), + color: AppColor.colorDefaultRichTextButton, + opacity: opacity, + excludeFromSemantics: true, + tooltip: RichTextStyleType.orderList.getTooltipButton(context) + ) + ), ), ), ) diff --git a/lib/features/composer/presentation/widgets/web/web_editor_widget.dart b/lib/features/composer/presentation/widgets/web/web_editor_widget.dart index 83dc646f58..6e5fea4f83 100644 --- a/lib/features/composer/presentation/widgets/web/web_editor_widget.dart +++ b/lib/features/composer/presentation/widgets/web/web_editor_widget.dart @@ -65,13 +65,9 @@ class WebEditorWidget extends StatefulWidget { class _WebEditorState extends State { - static const double _offsetHeight = 50; - static const double _offsetWidth = 90; static const double _defaultHtmlEditorHeight = 550; late HtmlEditorController _editorController; - double? dropZoneWidth; - double? dropZoneHeight; final ValueNotifier _htmlEditorHeight = ValueNotifier(_defaultHtmlEditorHeight); bool _dropListenerRegistered = false; Function(Event)? _dropListener; @@ -82,14 +78,8 @@ class _WebEditorState extends State { _editorController = widget.editorController; log('_WebEditorState::initState:height: ${widget.height} | width: ${widget.width}'); if (widget.height != null) { - dropZoneHeight = widget.height! - _offsetHeight; _htmlEditorHeight.value = widget.height ?? _defaultHtmlEditorHeight; } - if (widget.width != null) { - dropZoneWidth = widget.width! - _offsetWidth; - } - log('_WebEditorState::initState:dropZoneWidth: $dropZoneWidth | dropZoneHeight: $dropZoneHeight'); - _dropListener = (event) { if (event is MessageEvent) { if (jsonDecode(event.data)['name'] == HtmlUtils.registerDropListener.name) { @@ -134,66 +124,69 @@ class _WebEditorState extends State { return ValueListenableBuilder( valueListenable: _htmlEditorHeight, builder: (context, height, _) { - return HtmlEditor( - key: Key('web_editor_$height'), - controller: _editorController, - htmlEditorOptions: HtmlEditorOptions( - shouldEnsureVisible: true, - hint: '', - darkMode: false, - initialText: widget.content, - customBodyCssStyle: HtmlUtils.customCssStyleHtmlEditor(direction: widget.direction), - spellCheck: true, - disableDragAndDrop: true, - webInitialScripts: UnmodifiableListView([ - WebScript( - name: HtmlUtils.lineHeight100Percent.name, - script: HtmlUtils.lineHeight100Percent.script, - ), - WebScript( - name: HtmlUtils.registerDropListener.name, - script: HtmlUtils.registerDropListener.script, + return Semantics( + label: 'Composer:content', + child: HtmlEditor( + key: Key('web_editor_$height'), + controller: _editorController, + htmlEditorOptions: HtmlEditorOptions( + shouldEnsureVisible: true, + hint: '', + darkMode: false, + initialText: widget.content, + customBodyCssStyle: HtmlUtils.customCssStyleHtmlEditor(direction: widget.direction), + spellCheck: true, + disableDragAndDrop: true, + webInitialScripts: UnmodifiableListView([ + WebScript( + name: HtmlUtils.lineHeight100Percent.name, + script: HtmlUtils.lineHeight100Percent.script, + ), + WebScript( + name: HtmlUtils.registerDropListener.name, + script: HtmlUtils.registerDropListener.script, + ), + WebScript( + name: HtmlUtils.unregisterDropListener.name, + script: HtmlUtils.unregisterDropListener.script, + ) + ]) + ), + htmlToolbarOptions: const HtmlToolbarOptions( + toolbarType: ToolbarType.hide, + defaultToolbarButtons: [], + ), + otherOptions: OtherOptions( + height: height, + // dropZoneWidth: dropZoneWidth, + // dropZoneHeight: dropZoneHeight, + ), + callbacks: Callbacks( + onBeforeCommand: widget.onChangeContent, + onChangeContent: widget.onChangeContent, + onInit: () { + widget.onInitial?.call(widget.content); + if (!_dropListenerRegistered) { + _editorController.evaluateJavascriptWeb( + HtmlUtils.registerDropListener.name); + _dropListenerRegistered = true; + } + }, + onFocus: widget.onFocus, + onBlur: widget.onUnFocus, + onMouseDown: () => widget.onMouseDown?.call(context), + onChangeSelection: widget.onEditorSettings, + onChangeCodeview: widget.onChangeContent, + onTextFontSizeChanged: widget.onEditorTextSizeChanged, + onPaste: () => _editorController.evaluateJavascriptWeb( + HtmlUtils.lineHeight100Percent.name ), - WebScript( - name: HtmlUtils.unregisterDropListener.name, - script: HtmlUtils.unregisterDropListener.script, - ) - ]) - ), - htmlToolbarOptions: const HtmlToolbarOptions( - toolbarType: ToolbarType.hide, - defaultToolbarButtons: [], - ), - otherOptions: OtherOptions( - height: height, - // dropZoneWidth: dropZoneWidth, - // dropZoneHeight: dropZoneHeight, - ), - callbacks: Callbacks( - onBeforeCommand: widget.onChangeContent, - onChangeContent: widget.onChangeContent, - onInit: () { - widget.onInitial?.call(widget.content); - if (!_dropListenerRegistered) { - _editorController.evaluateJavascriptWeb( - HtmlUtils.registerDropListener.name); - _dropListenerRegistered = true; - } - }, - onFocus: widget.onFocus, - onBlur: widget.onUnFocus, - onMouseDown: () => widget.onMouseDown?.call(context), - onChangeSelection: widget.onEditorSettings, - onChangeCodeview: widget.onChangeContent, - onTextFontSizeChanged: widget.onEditorTextSizeChanged, - onPaste: () => _editorController.evaluateJavascriptWeb( - HtmlUtils.lineHeight100Percent.name + onDragEnter: widget.onDragEnter, + onDragLeave: (_) {}, + onImageUpload: widget.onPasteImageSuccessAction, + onImageUploadError: widget.onPasteImageFailureAction, + onInitialTextLoadComplete: widget.onInitialContentLoadComplete, ), - onDragEnter: widget.onDragEnter, - onDragLeave: (_) {}, - onImageUpload: widget.onPasteImageSuccessAction, - onImageUploadError: widget.onPasteImageFailureAction, - onInitialTextLoadComplete: widget.onInitialContentLoadComplete, ), ); } diff --git a/lib/features/contact/presentation/contact_view.dart b/lib/features/contact/presentation/contact_view.dart index fe2e23dcee..033d9507e3 100644 --- a/lib/features/contact/presentation/contact_view.dart +++ b/lib/features/contact/presentation/contact_view.dart @@ -27,7 +27,9 @@ class ContactView extends GetWidget { backgroundColor: Colors.black38, body: PointerInterceptor( child: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), + onTap: PlatformInfo.isWeb + ? null + : FocusScope.of(context).unfocus, child: SafeArea( bottom: false, left: false, @@ -41,131 +43,129 @@ class ContactView extends GetWidget { borderRadius: _getRadiusContactView(context), color: Colors.white ), - child: ClipRRect( - borderRadius: _getRadiusContactView(context), - child: SafeArea(child: Container( - color: Colors.white, - child: Column(children: [ - Container( - height: 52, - color: Colors.white, - padding: ContactUtils.getPaddingAppBar(context, controller.responsiveUtils), - child: AppBarContactWidget( - onCloseContactView: () => controller.closeContactView(context)) - ), - const Divider(color: AppColor.colorDividerComposer, height: 1), - SearchAppBarWidget( - imagePaths: controller.imagePaths, - searchQuery: controller.searchQuery.value, - searchFocusNode: controller.textInputSearchFocus, - searchInputController: controller.textInputSearchController, - hasBackButton: false, - hasSearchButton: true, - margin: ContactUtils.getPaddingSearchInputForm(context, controller.responsiveUtils), - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - color: AppColor.colorBgSearchBar), - iconClearText: SvgPicture.asset( - controller.imagePaths.icClearTextSearch, - width: 18, - height: 18, - fit: BoxFit.fill), - hintText: AppLocalizations.of(context).hintSearchInputContact, - onClearTextSearchAction: controller.clearAllTextInputSearchForm, - onTextChangeSearchAction: controller.onTextSearchChange, - onSearchTextAction: controller.onSearchTextAction, - ), - if (PlatformInfo.isWeb) - Obx(() { - final username = controller.session.value?.username.value ?? ''; - if (username.isNotEmpty) { - final userEmailAddress = EmailAddress( - AppLocalizations.of(context).me, - username); - final fromMeSuggestionEmailAddress = SuggestionEmailAddress(userEmailAddress, state: SuggestionEmailState.valid); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Column( - children: [ - ContactSuggestionBoxItem( - fromMeSuggestionEmailAddress, - padding: ContactUtils.getPaddingSearchResultList(context, controller.responsiveUtils), - selectedContactCallbackAction: (contact) { - controller.selectContact(context, contact); - }, - ), - Padding( - padding: ContactUtils.getPaddingDividerSearchResultList(context, controller.responsiveUtils), - child: const Divider(height: 1, color: AppColor.colorDivider), - ), - ], - ), - ); - } else { - return const SizedBox.shrink(); - } - }), - Expanded(child: Obx(() { - if (controller.listContactSearched.isNotEmpty) { - if (PlatformInfo.isMobile) { - return Container( - color: Colors.white, - child: ListView.separated( - itemCount: controller.listContactSearched.length, - separatorBuilder: (context, index) { - return Padding( - padding: ContactUtils.getPaddingDividerSearchResultList(context, controller.responsiveUtils), - child: const Divider(height: 1, color: AppColor.colorDivider), - ); + clipBehavior: Clip.antiAlias, + child: SafeArea(child: Container( + color: Colors.white, + child: Column(children: [ + Container( + height: 52, + color: Colors.white, + padding: ContactUtils.getPaddingAppBar(context, controller.responsiveUtils), + child: AppBarContactWidget( + onCloseContactView: () => controller.closeContactView(context)) + ), + const Divider(color: AppColor.colorDividerComposer, height: 1), + SearchAppBarWidget( + imagePaths: controller.imagePaths, + searchQuery: controller.searchQuery.value, + searchFocusNode: controller.textInputSearchFocus, + searchInputController: controller.textInputSearchController, + hasBackButton: false, + hasSearchButton: true, + margin: ContactUtils.getPaddingSearchInputForm(context, controller.responsiveUtils), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: AppColor.colorBgSearchBar), + iconClearText: SvgPicture.asset( + controller.imagePaths.icClearTextSearch, + width: 18, + height: 18, + fit: BoxFit.fill), + hintText: AppLocalizations.of(context).hintSearchInputContact, + onClearTextSearchAction: controller.clearAllTextInputSearchForm, + onTextChangeSearchAction: controller.onTextSearchChange, + onSearchTextAction: controller.onSearchTextAction, + ), + if (PlatformInfo.isWeb) + Obx(() { + final username = controller.session.value?.username.value ?? ''; + if (username.isNotEmpty) { + final userEmailAddress = EmailAddress( + AppLocalizations.of(context).me, + username); + final fromMeSuggestionEmailAddress = SuggestionEmailAddress(userEmailAddress, state: SuggestionEmailState.valid); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Column( + children: [ + ContactSuggestionBoxItem( + fromMeSuggestionEmailAddress, + padding: ContactUtils.getPaddingSearchResultList(context, controller.responsiveUtils), + selectedContactCallbackAction: (contact) { + controller.selectContact(context, contact); }, - itemBuilder: (context, index) { - final emailAddress = controller.listContactSearched[index]; - final suggestionEmailAddress = _toSuggestionEmailAddress( - emailAddress, - controller.contactSelected != null ? [controller.contactSelected!] : [] - ); - return ContactSuggestionBoxItem( - suggestionEmailAddress, - padding: ContactUtils.getPaddingSearchResultList(context, controller.responsiveUtils), - selectedContactCallbackAction: (contact) => controller.selectContact(context, contact), - ); - } - ) - ); - } else { - return Container( - color: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 4), - child: ListView.separated( - itemCount: controller.listContactSearched.length, - separatorBuilder: (context, index) { - return Padding( - padding: ContactUtils.getPaddingDividerSearchResultList(context, controller.responsiveUtils), - child: const Divider(height: 1, color: AppColor.colorDivider), - ); - }, - itemBuilder: (context, index) { - final emailAddress = controller.listContactSearched[index]; - final suggestionEmailAddress = _toSuggestionEmailAddress( - emailAddress, - controller.contactSelected != null ? [controller.contactSelected!] : [] - ); - return ContactSuggestionBoxItem( - suggestionEmailAddress, - padding: ContactUtils.getPaddingSearchResultList(context, controller.responsiveUtils), - selectedContactCallbackAction: (contact) => controller.selectContact(context, contact), - ); - } - ) - ); - } - } else { - return const SizedBox.shrink(); - } - })), - ]), - )) - ) + ), + Padding( + padding: ContactUtils.getPaddingDividerSearchResultList(context, controller.responsiveUtils), + child: const Divider(height: 1, color: AppColor.colorDivider), + ), + ], + ), + ); + } else { + return const SizedBox.shrink(); + } + }), + Expanded(child: Obx(() { + if (controller.listContactSearched.isNotEmpty) { + if (PlatformInfo.isMobile) { + return Container( + color: Colors.white, + child: ListView.separated( + itemCount: controller.listContactSearched.length, + separatorBuilder: (context, index) { + return Padding( + padding: ContactUtils.getPaddingDividerSearchResultList(context, controller.responsiveUtils), + child: const Divider(height: 1, color: AppColor.colorDivider), + ); + }, + itemBuilder: (context, index) { + final emailAddress = controller.listContactSearched[index]; + final suggestionEmailAddress = _toSuggestionEmailAddress( + emailAddress, + controller.contactSelected != null ? [controller.contactSelected!] : [] + ); + return ContactSuggestionBoxItem( + suggestionEmailAddress, + padding: ContactUtils.getPaddingSearchResultList(context, controller.responsiveUtils), + selectedContactCallbackAction: (contact) => controller.selectContact(context, contact), + ); + } + ) + ); + } else { + return Container( + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 4), + child: ListView.separated( + itemCount: controller.listContactSearched.length, + separatorBuilder: (context, index) { + return Padding( + padding: ContactUtils.getPaddingDividerSearchResultList(context, controller.responsiveUtils), + child: const Divider(height: 1, color: AppColor.colorDivider), + ); + }, + itemBuilder: (context, index) { + final emailAddress = controller.listContactSearched[index]; + final suggestionEmailAddress = _toSuggestionEmailAddress( + emailAddress, + controller.contactSelected != null ? [controller.contactSelected!] : [] + ); + return ContactSuggestionBoxItem( + suggestionEmailAddress, + padding: ContactUtils.getPaddingSearchResultList(context, controller.responsiveUtils), + selectedContactCallbackAction: (contact) => controller.selectContact(context, contact), + ); + } + ) + ); + } + } else { + return const SizedBox.shrink(); + } + })), + ]), + )) ), ), ), diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 2b9e09d765..5a54413c3b 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -1515,6 +1515,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { ) ), barrierColor: AppColor.colorDefaultCupertinoActionSheet, + barrierDismissible: false ); } } diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index cfc862c734..7361383baf 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -15,6 +15,7 @@ import 'package:model/extensions/presentation_email_extension.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/base/key_values/email_details_key_values.dart'; import 'package:tmail_ui_user/features/base/widget/popup_item_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart'; @@ -77,174 +78,177 @@ class EmailView extends GetWidget { right: controller.responsiveUtils.isLandscapeMobile(context), left: controller.responsiveUtils.isLandscapeMobile(context), bottom: !PlatformInfo.isIOS, - child: Container( - clipBehavior: Clip.antiAlias, - decoration: controller.responsiveUtils.isWebDesktop(context) - ? BoxDecoration( - borderRadius: BorderRadius.circular(20), - border: Border.all(color: AppColor.colorBorderBodyThread, width: 1), - color: Colors.white) - : const BoxDecoration(color: Colors.white), - margin: _getMarginEmailView(context), - child: Obx(() { - final currentEmail = controller.currentEmail; - if (currentEmail != null) { - return Column(children: [ - if (!PlatformInfo.isIOS) - Obx(() => EmailViewAppBarWidget( - key: const Key('email_view_app_bar_widget'), - presentationEmail: currentEmail, - mailboxContain: _getMailboxContain(currentEmail), - isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning, - onBackAction: () => controller.closeEmailView(context: context), - onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action), - onMoreActionClick: (presentationEmail, position) => _handleMoreEmailAction(context: context, presentationEmail: presentationEmail, position: position), - optionsWidget: PlatformInfo.isWeb && controller.emailSupervisorController.supportedPageView.isTrue - ? _buildNavigatorPageViewWidgets(context) - : null, - )), - Obx(() { - final vacation = controller.mailboxDashBoardController.vacationResponse.value; - if (vacation?.vacationResponderIsValid == true && - ( - controller.responsiveUtils.isMobile(context) || - controller.responsiveUtils.isTablet(context) || - controller.responsiveUtils.isLandscapeMobile(context) - ) - ) { - return VacationNotificationMessageWidget( - margin: const EdgeInsetsDirectional.only(start: 12, end: 12, top: 8), - vacationResponse: vacation!, - actionGotoVacationSetting: controller.mailboxDashBoardController.goToVacationSetting, - actionEndNow: controller.mailboxDashBoardController.disableVacationResponder - ); - } else { - return const SizedBox.shrink(); - } - }), - Expanded( - child: LayoutBuilder(builder: (context, constraints) { - return Obx(() { - bool supportedPageView = controller.emailSupervisorController.supportedPageView.isTrue && PlatformInfo.isMobile; - final currentListEmail = controller.emailSupervisorController.currentListEmail; - - if (supportedPageView) { - return PageView.builder( - physics: controller.emailSupervisorController.scrollPhysicsPageView.value, - itemCount: currentListEmail.length, - allowImplicitScrolling: true, - controller: controller.emailSupervisorController.pageController, - onPageChanged: controller.emailSupervisorController.onPageChanged, - itemBuilder: (context, index) { - final currentEmail = currentListEmail[index]; - if (PlatformInfo.isMobile) { - return SingleChildScrollView( - physics : const ClampingScrollPhysics(), - child: Container( - width: double.infinity, - alignment: Alignment.center, - color: Colors.white, - child: Obx(() => _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - calendarEvent: controller.calendarEvent, - maxBodyHeight: constraints.maxHeight - )) - ) - ); - } else { - return Obx(() { - final calendarEvent = controller.calendarEvent; - if (currentEmail.hasCalendarEvent && calendarEvent != null) { - return Padding( - padding: const EdgeInsetsDirectional.symmetric(horizontal: 4), - child: SingleChildScrollView( - physics : const ClampingScrollPhysics(), - child: Container( - width: double.infinity, - alignment: Alignment.center, - color: Colors.white, - child: _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - calendarEvent: calendarEvent, - emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(), - ) - ) - ), - ); - } else { - return _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - maxBodyHeight: constraints.maxHeight - ); - } - }); - } - } - ); - } else { - if (PlatformInfo.isMobile) { - return SingleChildScrollView( - physics : const ClampingScrollPhysics(), - child: Container( - width: double.infinity, - alignment: Alignment.center, - color: Colors.white, - child: Obx(() => _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - calendarEvent: controller.calendarEvent, - maxBodyHeight: constraints.maxHeight - )) - ) - ); - } else { - return Obx(() { - final calendarEvent = controller.calendarEvent; - if (currentEmail.hasCalendarEvent && calendarEvent != null) { - return Padding( - padding: const EdgeInsetsDirectional.symmetric(horizontal: 4), - child: SingleChildScrollView( + child: Semantics( + identifier: EmailDetailsKeyValues.emailDetails, + child: Container( + clipBehavior: Clip.antiAlias, + decoration: controller.responsiveUtils.isWebDesktop(context) + ? BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColor.colorBorderBodyThread, width: 1), + color: Colors.white) + : const BoxDecoration(color: Colors.white), + margin: _getMarginEmailView(context), + child: Obx(() { + final currentEmail = controller.currentEmail; + if (currentEmail != null) { + return Column(children: [ + if (!PlatformInfo.isIOS) + Obx(() => EmailViewAppBarWidget( + key: const Key('email_view_app_bar_widget'), + presentationEmail: currentEmail, + mailboxContain: _getMailboxContain(currentEmail), + isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning, + onBackAction: () => controller.closeEmailView(context: context), + onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action), + onMoreActionClick: (presentationEmail, position) => _handleMoreEmailAction(context: context, presentationEmail: presentationEmail, position: position), + optionsWidget: PlatformInfo.isWeb && controller.emailSupervisorController.supportedPageView.isTrue + ? _buildNavigatorPageViewWidgets(context) + : null, + )), + Obx(() { + final vacation = controller.mailboxDashBoardController.vacationResponse.value; + if (vacation?.vacationResponderIsValid == true && + ( + controller.responsiveUtils.isMobile(context) || + controller.responsiveUtils.isTablet(context) || + controller.responsiveUtils.isLandscapeMobile(context) + ) + ) { + return VacationNotificationMessageWidget( + margin: const EdgeInsetsDirectional.only(start: 12, end: 12, top: 8), + vacationResponse: vacation!, + actionGotoVacationSetting: controller.mailboxDashBoardController.goToVacationSetting, + actionEndNow: controller.mailboxDashBoardController.disableVacationResponder + ); + } else { + return const SizedBox.shrink(); + } + }), + Expanded( + child: LayoutBuilder(builder: (context, constraints) { + return Obx(() { + bool supportedPageView = controller.emailSupervisorController.supportedPageView.isTrue && PlatformInfo.isMobile; + final currentListEmail = controller.emailSupervisorController.currentListEmail; + + if (supportedPageView) { + return PageView.builder( + physics: controller.emailSupervisorController.scrollPhysicsPageView.value, + itemCount: currentListEmail.length, + allowImplicitScrolling: true, + controller: controller.emailSupervisorController.pageController, + onPageChanged: controller.emailSupervisorController.onPageChanged, + itemBuilder: (context, index) { + final currentEmail = currentListEmail[index]; + if (PlatformInfo.isMobile) { + return SingleChildScrollView( physics : const ClampingScrollPhysics(), child: Container( width: double.infinity, alignment: Alignment.center, color: Colors.white, - child: _buildEmailMessage( + child: Obx(() => _buildEmailMessage( context: context, presentationEmail: currentEmail, - calendarEvent: calendarEvent, - emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(), + calendarEvent: controller.calendarEvent, maxBodyHeight: constraints.maxHeight - ) + )) ) - ), - ); - } else { - return _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - maxBodyHeight: constraints.maxHeight - ); + ); + } else { + return Obx(() { + final calendarEvent = controller.calendarEvent; + if (currentEmail.hasCalendarEvent && calendarEvent != null) { + return Padding( + padding: const EdgeInsetsDirectional.symmetric(horizontal: 4), + child: SingleChildScrollView( + physics : const ClampingScrollPhysics(), + child: Container( + width: double.infinity, + alignment: Alignment.center, + color: Colors.white, + child: _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + calendarEvent: calendarEvent, + emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(), + ) + ) + ), + ); + } else { + return _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + maxBodyHeight: constraints.maxHeight + ); + } + }); + } } - }); + ); + } else { + if (PlatformInfo.isMobile) { + return SingleChildScrollView( + physics : const ClampingScrollPhysics(), + child: Container( + width: double.infinity, + alignment: Alignment.center, + color: Colors.white, + child: Obx(() => _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + calendarEvent: controller.calendarEvent, + maxBodyHeight: constraints.maxHeight + )) + ) + ); + } else { + return Obx(() { + final calendarEvent = controller.calendarEvent; + if (currentEmail.hasCalendarEvent && calendarEvent != null) { + return Padding( + padding: const EdgeInsetsDirectional.symmetric(horizontal: 4), + child: SingleChildScrollView( + physics : const ClampingScrollPhysics(), + child: Container( + width: double.infinity, + alignment: Alignment.center, + color: Colors.white, + child: _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + calendarEvent: calendarEvent, + emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(), + maxBodyHeight: constraints.maxHeight + ) + ) + ), + ); + } else { + return _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + maxBodyHeight: constraints.maxHeight + ); + } + }); + } } - } - }); - }), - ), - EmailViewBottomBarWidget( - key: const Key('email_view_button_bar'), - presentationEmail: currentEmail, - emailActionCallback: controller.pressEmailAction - ), - ]); - } else { - return const EmailViewEmptyWidget(); - } - }) + }); + }), + ), + EmailViewBottomBarWidget( + key: const Key('email_view_button_bar'), + presentationEmail: currentEmail, + emailActionCallback: controller.pressEmailAction + ), + ]); + } else { + return const EmailViewEmptyWidget(); + } + }) + ), ) ) ), @@ -559,6 +563,7 @@ class EmailView extends GetWidget { ) { return actionTypes.map((action) { return PopupMenuItem( + enabled: false, key: Key('${action.name}_action'), padding: EdgeInsets.zero, child: PopupItemWidget( diff --git a/lib/features/email/presentation/widgets/email_receiver_widget.dart b/lib/features/email/presentation/widgets/email_receiver_widget.dart index 636caaf1c5..8278867728 100644 --- a/lib/features/email/presentation/widgets/email_receiver_widget.dart +++ b/lib/features/email/presentation/widgets/email_receiver_widget.dart @@ -13,6 +13,7 @@ import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/email_address_extension.dart'; import 'package:model/extensions/list_email_address_extension.dart'; import 'package:model/extensions/presentation_email_extension.dart'; +import 'package:tmail_ui_user/features/base/key_values/email_details_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_sender_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/prefix_recipient_widget.dart'; @@ -231,18 +232,21 @@ class _EmailReceiverWidgetState extends State { List _buildRecipientsTag({required List listEmailAddress}) { return listEmailAddress - .mapIndexed((index, emailAddress) => TMailButtonWidget.fromText( - text: index == listEmailAddress.length - 1 - ? emailAddress.asString() - : '${emailAddress.asString()},', - textStyle: Theme.of(context).textTheme.labelSmall?.copyWith( - color: Colors.black, - fontSize: 16, + .mapIndexed((index, emailAddress) => Semantics( + identifier: EmailDetailsKeyValues.recipient, + child: TMailButtonWidget.fromText( + text: index == listEmailAddress.length - 1 + ? emailAddress.asString() + : '${emailAddress.asString()},', + textStyle: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.black, + fontSize: 16, + ), + padding: const EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8), + backgroundColor: Colors.transparent, + onTapActionCallback: () => widget.openEmailAddressDetailAction?.call(context, emailAddress), + onLongPressActionCallback: () => AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress), ), - padding: const EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8), - backgroundColor: Colors.transparent, - onTapActionCallback: () => widget.openEmailAddressDetailAction?.call(context, emailAddress), - onLongPressActionCallback: () => AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress), )) .toList(); } diff --git a/lib/features/email/presentation/widgets/email_sender_builder.dart b/lib/features/email/presentation/widgets/email_sender_builder.dart index 70a399e73f..c9b707ae36 100644 --- a/lib/features/email/presentation/widgets/email_sender_builder.dart +++ b/lib/features/email/presentation/widgets/email_sender_builder.dart @@ -4,6 +4,7 @@ import 'package:core/presentation/utils/style_utils.dart'; import 'package:flutter/material.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/extensions/email_address_extension.dart'; +import 'package:tmail_ui_user/features/base/key_values/email_details_key_values.dart'; import 'package:tmail_ui_user/features/base/widget/material_text_button.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; @@ -30,35 +31,41 @@ class EmailSenderBuilder extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ if (emailAddress.displayName.isNotEmpty) - MaterialTextButton( - label: emailAddress.displayName, - onTap: () => openEmailAddressDetailAction?.call(context, emailAddress), - onLongPress: () { - AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); - }, - borderRadius: 8, - padding: const EdgeInsets.all(3), - labelSize: 20, - labelColor: Colors.black, - labelWeight: FontWeight.bold, - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap + Semantics( + identifier: EmailDetailsKeyValues.sender, + child: MaterialTextButton( + label: emailAddress.displayName, + onTap: () => openEmailAddressDetailAction?.call(context, emailAddress), + onLongPress: () { + AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); + }, + borderRadius: 8, + padding: const EdgeInsets.all(3), + labelSize: 20, + labelColor: Colors.black, + labelWeight: FontWeight.bold, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap + ), ), Padding( padding: const EdgeInsets.only(top: 2), - child: MaterialTextButton( - label: '<${emailAddress.emailAddress}>', - onTap: () => openEmailAddressDetailAction?.call(context, emailAddress), - onLongPress: () { - AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); - }, - borderRadius: 8, - padding: const EdgeInsets.all(3), - labelSize: 16, - labelColor: AppColor.colorEmailAddressFull, - labelWeight: FontWeight.w500, - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap + child: Semantics( + identifier: EmailDetailsKeyValues.senderEmail, + child: MaterialTextButton( + label: '<${emailAddress.emailAddress}>', + onTap: () => openEmailAddressDetailAction?.call(context, emailAddress), + onLongPress: () { + AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); + }, + borderRadius: 8, + padding: const EdgeInsets.all(3), + labelSize: 16, + labelColor: AppColor.colorEmailAddressFull, + labelWeight: FontWeight.w500, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap + ), ), ) ] diff --git a/lib/features/email/presentation/widgets/email_subject_widget.dart b/lib/features/email/presentation/widgets/email_subject_widget.dart index 6132c433c6..94a9592907 100644 --- a/lib/features/email/presentation/widgets/email_subject_widget.dart +++ b/lib/features/email/presentation/widgets/email_subject_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:model/email/presentation_email.dart'; +import 'package:tmail_ui_user/features/base/key_values/email_details_key_values.dart'; import 'package:tmail_ui_user/features/email/presentation/styles/email_subject_styles.dart'; class EmailSubjectWidget extends StatelessWidget { @@ -12,14 +13,18 @@ class EmailSubjectWidget extends StatelessWidget { Widget build(BuildContext context) { return Padding( padding: EmailSubjectStyles.padding, - child: Text( - presentationEmail.getEmailTitle(), - maxLines: EmailSubjectStyles.maxLines, - style: const TextStyle( - fontSize: EmailSubjectStyles.textSize, - color: EmailSubjectStyles.textColor, - fontWeight: EmailSubjectStyles.fontWeight - ) + child: Semantics( + identifier: EmailDetailsKeyValues.subject, + container: true, + child: Text( + presentationEmail.getEmailTitle(), + maxLines: EmailSubjectStyles.maxLines, + style: const TextStyle( + fontSize: EmailSubjectStyles.textSize, + color: EmailSubjectStyles.textColor, + fontWeight: EmailSubjectStyles.fontWeight + ) + ), ) ); } diff --git a/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart b/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart index efbc919b1f..4aade8fb0a 100644 --- a/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart +++ b/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart @@ -1,5 +1,4 @@ import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/views/checkbox/labeled_checkbox.dart'; import 'package:flutter/material.dart'; import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger_mobile.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -29,13 +28,21 @@ class CheckBoxHasAttachmentWidget extends StatelessWidget { nextFocusNode?.requestFocus(); } }, - child: LabeledCheckbox( - label: AppLocalizations.of(context).hasAttachment, - focusNode: currentFocusNode, - contentPadding: EdgeInsets.zero, + child: CheckboxListTile( value: hasAttachmentValue, + contentPadding: const EdgeInsetsDirectional.only(start: 4), activeColor: AppColor.primaryColor, - onChanged: onChanged, + focusNode: currentFocusNode, + controlAffinity: ListTileControlAffinity.leading, + onChanged: onChanged, + title: Text( + AppLocalizations.of(context).hasAttachment, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black + ), + ), ) ); } diff --git a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart b/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart index 32cc96f31d..7fc9cc36c1 100644 --- a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart +++ b/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart @@ -108,29 +108,32 @@ class DateSelectionDropDownButton extends StatelessWidget { ) { return DropdownMenuItem( value: recoveryTime, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: DateSelectionDropdownButtonStyles.height, - child: Row( - children: [ - Expanded( - child: Text( - recoveryTime.getTitle(context), - style: DateSelectionDropdownButtonStyles.selectedValueTexStyle, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - ) - ), - if (recoveryTime == recoveryTimeSelected) - SvgPicture.asset( - imagePaths.icChecked, - width: DateSelectionDropdownButtonStyles.checkedIconSize, - height: DateSelectionDropdownButtonStyles.checkedIconSize, - fit: BoxFit.fill - ) - ] + child: Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: DateSelectionDropdownButtonStyles.height, + child: Row( + children: [ + Expanded( + child: Text( + recoveryTime.getTitle(context), + style: DateSelectionDropdownButtonStyles.selectedValueTexStyle, + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + ) + ), + if (recoveryTime == recoveryTimeSelected) + SvgPicture.asset( + imagePaths.icChecked, + width: DateSelectionDropdownButtonStyles.checkedIconSize, + height: DateSelectionDropdownButtonStyles.checkedIconSize, + fit: BoxFit.fill + ) + ] + ), ), ), ), diff --git a/lib/features/login/presentation/base_login_view.dart b/lib/features/login/presentation/base_login_view.dart index 9c1b75591d..ba6dfa1db1 100644 --- a/lib/features/login/presentation/base_login_view.dart +++ b/lib/features/login/presentation/base_login_view.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/key_values/login_basic_auth_key_values.dart'; import 'package:tmail_ui_user/features/base/widget/recent_item_tile_widget.dart'; import 'package:tmail_ui_user/features/login/domain/model/recent_login_username.dart'; import 'package:tmail_ui_user/features/login/presentation/login_controller.dart'; @@ -17,21 +18,24 @@ abstract class BaseLoginView extends GetWidget { margin: const EdgeInsetsDirectional.only(bottom: 16, start: 24, end: 24), width: controller.responsiveUtils.getDeviceWidth(context), height: 48, - child: ElevatedButton( - key: const Key('loginSubmitForm'), - style: ElevatedButton.styleFrom( - foregroundColor: Colors.white, - backgroundColor: AppColor.primaryColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - side: const BorderSide(width: 0, color: AppColor.primaryColor) + child: Semantics( + identifier: LoginBasicAuthKeyValues.loginSignInButton, + child: ElevatedButton( + key: const ValueKey(LoginBasicAuthKeyValues.loginSignInButton), + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: AppColor.primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: const BorderSide(width: 0, color: AppColor.primaryColor) + ) + ), + onPressed: controller.handleLoginPressed, + child: Text( + AppLocalizations.of(context).signIn, + style: const TextStyle(fontSize: 16, color: Colors.white) ) ), - onPressed: controller.handleLoginPressed, - child: Text( - AppLocalizations.of(context).signIn, - style: const TextStyle(fontSize: 16, color: Colors.white) - ) ) ); } @@ -54,7 +58,7 @@ abstract class BaseLoginView extends GetWidget { Widget buildUserNameInput(BuildContext context) { return TypeAheadFormFieldBuilder( - key: const Key('login_username_input'), + key: const ValueKey(LoginBasicAuthKeyValues.loginEmailField), controller: controller.usernameInputController, onTextChange: controller.onUsernameChange, focusNode: controller.userNameFocusNode, @@ -79,7 +83,7 @@ abstract class BaseLoginView extends GetWidget { Widget buildPasswordInput(BuildContext context) { return LoginTextInputBuilder( - key: const Key('login_password_input'), + key: const ValueKey(LoginBasicAuthKeyValues.loginPasswordField), controller: controller.passwordInputController, autofillHints: const [AutofillHints.password], textInputAction: TextInputAction.done, diff --git a/lib/features/login/presentation/login_view.dart b/lib/features/login/presentation/login_view.dart index 28592482e7..f2294ee425 100644 --- a/lib/features/login/presentation/login_view.dart +++ b/lib/features/login/presentation/login_view.dart @@ -4,6 +4,9 @@ import 'package:core/presentation/utils/theme_utils.dart'; import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/key_values/login_basic_auth_key_values.dart'; +import 'package:tmail_ui_user/features/base/key_values/login_key_values.dart'; +import 'package:tmail_ui_user/features/base/widget/application_version_widget.dart'; import 'package:tmail_ui_user/features/base/widget/recent_item_tile_widget.dart'; import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart'; import 'package:tmail_ui_user/features/login/presentation/base_login_view.dart'; @@ -27,6 +30,7 @@ class LoginView extends BaseLoginView { return Scaffold( backgroundColor: AppColor.primaryLightColor, + key: const Key(LoginBasicAuthKeyValues.loginView), body: GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Container( @@ -90,6 +94,7 @@ class LoginView extends BaseLoginView { case LoginFormType.dnsLookupForm: case LoginFormType.retry: return DNSLookupInputForm( + key: const ValueKey(LoginKeyValues.loginEmailField), textEditingController: controller.usernameInputController, onTextChange: controller.onUsernameChange, onTextSubmitted: (_) => controller.invokeDNSLookupToGetJmapUrl(), @@ -117,6 +122,7 @@ class LoginView extends BaseLoginView { padding: EdgeInsets.only(top: 16), child: PrivacyLinkWidget(), ), + const ApplicationVersionWidget(), ] ), ) @@ -127,6 +133,7 @@ class LoginView extends BaseLoginView { return Padding( padding: const EdgeInsets.only(right: 24, left: 24, bottom: 24), child: TypeAheadFormFieldBuilder( + key: const ValueKey(LoginKeyValues.loginHostUrlField), controller: controller.urlInputController, textInputAction: TextInputAction.next, keyboardType: TextInputType.url, @@ -162,7 +169,7 @@ class LoginView extends BaseLoginView { margin: const EdgeInsets.only(bottom: 16, left: 24, right: 24), width: controller.responsiveUtils.getDeviceWidth(context),height: 48, child: ElevatedButton( - key: const Key('nextToCredentialForm'), + key: const ValueKey(LoginKeyValues.loginNextButton), style: ElevatedButton.styleFrom( foregroundColor: Colors.white, backgroundColor: AppColor.primaryColor, diff --git a/lib/features/login/presentation/login_view_web.dart b/lib/features/login/presentation/login_view_web.dart index b56034117a..025acb16ec 100644 --- a/lib/features/login/presentation/login_view_web.dart +++ b/lib/features/login/presentation/login_view_web.dart @@ -5,7 +5,9 @@ import 'package:core/presentation/views/text/slogan_builder.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; +import 'package:tmail_ui_user/features/base/key_values/login_basic_auth_key_values.dart'; import 'package:tmail_ui_user/features/base/widget/application_logo_with_text_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/application_version_widget.dart'; import 'package:tmail_ui_user/features/login/presentation/base_login_view.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/privacy_link_widget.dart'; @@ -22,10 +24,14 @@ class LoginView extends BaseLoginView { return Scaffold( backgroundColor: AppColor.primaryLightColor, body: Center(child: SingleChildScrollView( - child: ResponsiveWidget( - responsiveUtils: controller.responsiveUtils, - mobile: _buildMobileForm(context), - desktop: _buildWebForm(context), + child: Semantics( + identifier: LoginBasicAuthKeyValues.loginView, + container: true, + child: ResponsiveWidget( + responsiveUtils: controller.responsiveUtils, + mobile: _buildMobileForm(context), + desktop: _buildWebForm(context), + ), ))), ); } @@ -72,6 +78,7 @@ class LoginView extends BaseLoginView { padding: EdgeInsets.only(top: 16), child: PrivacyLinkWidget(), ), + const ApplicationVersionWidget(), ], ) ), @@ -219,6 +226,7 @@ class LoginView extends BaseLoginView { padding: EdgeInsets.only(top: 16), child: PrivacyLinkWidget() ), + const ApplicationVersionWidget(), ], ) ), diff --git a/lib/features/mailbox/presentation/mailbox_view_web.dart b/lib/features/mailbox/presentation/mailbox_view_web.dart index 88d40f1611..54699a84ab 100644 --- a/lib/features/mailbox/presentation/mailbox_view_web.dart +++ b/lib/features/mailbox/presentation/mailbox_view_web.dart @@ -284,6 +284,7 @@ class MailboxView extends BaseMailboxView { parent: Obx(() => MailboxItemWidget( mailboxNode: mailboxNode, mailboxNodeSelected: controller.mailboxDashBoardController.selectedMailbox.value, + itemExcludeFromSemantics: true, onOpenMailboxFolderClick: (mailboxNode) => controller.openMailbox(context, mailboxNode.item), onExpandFolderActionClick: (mailboxNode) => controller.toggleMailboxFolder(mailboxNode, controller.mailboxListScrollController), onSelectMailboxFolderClick: controller.selectMailboxNode, @@ -306,6 +307,7 @@ class MailboxView extends BaseMailboxView { return Obx(() => MailboxItemWidget( mailboxNode: mailboxNode, mailboxNodeSelected: controller.mailboxDashBoardController.selectedMailbox.value, + itemExcludeFromSemantics: true, onOpenMailboxFolderClick: (mailboxNode) => controller.openMailbox(context, mailboxNode.item), onSelectMailboxFolderClick: controller.selectMailboxNode, onDragItemAccepted: _handleDragItemAccepted, diff --git a/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart b/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart index cd49df0eae..6e67920f4d 100644 --- a/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart +++ b/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart @@ -258,6 +258,7 @@ mixin MailboxWidgetMixin { } ) { return PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: AbsorbPointer( absorbing: !contextMenuItem.isActivated, diff --git a/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart b/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart index dba008eb3b..9af79016eb 100644 --- a/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart +++ b/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart @@ -25,6 +25,7 @@ class MailboxItemWidget extends StatefulWidget { final PresentationMailbox? mailboxNodeSelected; final MailboxActions? mailboxActions; final MailboxId? mailboxIdAlreadySelected; + final bool itemExcludeFromSemantics; final OnClickExpandMailboxNodeAction? onExpandFolderActionClick; final OnClickOpenMailboxNodeAction? onOpenMailboxFolderClick; @@ -39,6 +40,7 @@ class MailboxItemWidget extends StatefulWidget { required this.mailboxNode, this.selectionMode = SelectMode.INACTIVE, this.mailboxDisplayed = MailboxDisplayed.mailbox, + this.itemExcludeFromSemantics = false, this.mailboxNodeSelected, this.mailboxActions, this.mailboxIdAlreadySelected, @@ -70,6 +72,7 @@ class _MailboxItemWidgetState extends State { return InkWell( onTap: () => widget.onOpenMailboxFolderClick?.call(widget.mailboxNode), onHover: (value) => setState(() => _isItemHovered = value), + excludeFromSemantics: widget.itemExcludeFromSemantics, child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(MailboxItemWidgetStyles.borderRadius)), @@ -111,6 +114,7 @@ class _MailboxItemWidgetState extends State { return InkWell( onTap: () => widget.onOpenMailboxFolderClick?.call(widget.mailboxNode), onHover: (value) => setState(() => _isItemHovered = value), + excludeFromSemantics: widget.itemExcludeFromSemantics, child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(MailboxItemWidgetStyles.borderRadius)), @@ -151,6 +155,7 @@ class _MailboxItemWidgetState extends State { onTap: () => widget.selectionMode == SelectMode.ACTIVE ? widget.onSelectMailboxFolderClick?.call(widget.mailboxNode) : widget.onOpenMailboxFolderClick?.call(widget.mailboxNode), + excludeFromSemantics: widget.itemExcludeFromSemantics, borderRadius: const BorderRadius.all(Radius.circular(MailboxItemWidgetStyles.borderRadius)), child: Container( decoration: BoxDecoration( @@ -210,6 +215,7 @@ class _MailboxItemWidgetState extends State { onTap: () => !_isSelectActionNoValid ? widget.onOpenMailboxFolderClick?.call(widget.mailboxNode) : null, + excludeFromSemantics: widget.itemExcludeFromSemantics, customBorder: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8))), hoverColor: AppColor.colorMailboxHovered, child: Container( diff --git a/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart b/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart index f6c1229226..9f67109e44 100644 --- a/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart +++ b/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart @@ -1,7 +1,6 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/utils/style_utils.dart'; import 'package:core/presentation/views/text/text_field_builder.dart'; -import 'package:core/utils/app_logger.dart'; import 'package:core/utils/direction_utils.dart'; import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; @@ -25,7 +24,6 @@ class MailboxCreatorView extends GetWidget { @override Widget build(BuildContext context) { - log('MailboxCreatorView::build():'); return PointerInterceptor( child: GestureDetector( onTap: () => controller.closeMailboxCreator(context), @@ -40,7 +38,9 @@ class MailboxCreatorView extends GetWidget { right: false, child: Center( child: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), + onTap: PlatformInfo.isMobile + ? FocusScope.of(context).unfocus + : () {}, child: Container( margin: _getMarginView(context), decoration: BoxDecoration( @@ -60,20 +60,18 @@ class MailboxCreatorView extends GetWidget { ]), width: _getWidthView(context), height: _getHeightView(context), - child: ClipRRect( - borderRadius: _getRadiusView(context), - child: SafeArea( - top: false, - bottom: false, - left: PlatformInfo.isMobile && controller.responsiveUtils.isLandscapeMobile(context), - right: PlatformInfo.isMobile && controller.responsiveUtils.isLandscapeMobile(context), - child: Column(children: [ - _buildAppBar(context), - const Divider(color: AppColor.colorDividerDestinationPicker, height: 1), - _buildCreateMailboxNameInput(context), - _buildMailboxLocation(context), - ]), - ) + clipBehavior: Clip.antiAlias, + child: SafeArea( + top: false, + bottom: false, + left: PlatformInfo.isMobile && controller.responsiveUtils.isLandscapeMobile(context), + right: PlatformInfo.isMobile && controller.responsiveUtils.isLandscapeMobile(context), + child: Column(children: [ + _buildAppBar(context), + const Divider(color: AppColor.colorDividerDestinationPicker, height: 1), + _buildCreateMailboxNameInput(context), + _buildMailboxLocation(context), + ]), ) ), ) diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index f13d240849..e3511631b5 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -27,7 +27,9 @@ import 'package:model/model.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:rxdart/transformers.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart'; +import 'package:tmail_ui_user/features/base/state/button_state.dart'; import 'package:tmail_ui_user/features/composer/domain/exceptions/set_method_exception.dart'; import 'package:tmail_ui_user/features/composer/domain/extensions/email_request_extension.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; @@ -240,6 +242,7 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo PresentationMailbox? outboxMailbox; ComposerArguments? composerArguments; List? _identities; + ButtonState _fromMeSearchFilterButtonState = ButtonState.disabled; late StreamSubscription _emailAddressStreamSubscription; late StreamSubscription _emailContentStreamSubscription; @@ -1506,7 +1509,7 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo } result = await Get.to( - () => const ComposerView(), + () => const ComposerView(key: ValueKey(ComposerKeyValues.composerView)), binding: ComposerBindings(), opaque: false, arguments: argumentsWithIdentity); @@ -1673,6 +1676,10 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo if (accountId.value == null || sessionCurrent == null) { logError('MailboxDashBoardController::selectQuickSearchFilterAction(): accountId or sessionCurrent is null'); } + if (_fromMeSearchFilterButtonState == ButtonState.enabled) { + return; + } + _fromMeSearchFilterButtonState = ButtonState.enabled; final listContactSelected = searchController.searchEmailFilter.value.from; final arguments = ContactArguments(accountId.value!, sessionCurrent!, listContactSelected); @@ -1682,6 +1689,8 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo selectQuickSearchFilterFrom(newContact); dispatchAction(StartSearchEmailAction(filter: filter)); } + + _fromMeSearchFilterButtonState = ButtonState.disabled; } else { selectQuickSearchFilter(filter); dispatchAction(StartSearchEmailAction(filter: filter)); @@ -2723,6 +2732,7 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo sessionCurrent = null; mapMailboxById = {}; mapDefaultMailboxIdByRole = {}; + _fromMeSearchFilterButtonState = ButtonState.disabled; super.onClose(); } } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index 0aa186da8c..27e05e6a08 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/extensions/username_extension.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/base/widget/popup_item_no_icon_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_view_web.dart'; import 'package:tmail_ui_user/features/email/presentation/email_view.dart'; @@ -51,101 +52,98 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { ResponsiveWidget( responsiveUtils: controller.responsiveUtils, desktop: Scaffold( - body: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), - child: Container( - color: AppColor.colorBgDesktop, - child: Column(children: [ - Obx(() { - final accountId = controller.accountId.value; - if (accountId == null) { - return const SizedBox.shrink(); - } else { - return NavigationBarWidget( - avatarUserName: controller.sessionCurrent?.username.firstCharacter ?? '', - searchForm: SearchInputFormWidget(), - appGridController: controller.appGridDashboardController, - onShowAppDashboardAction: controller.showAppDashboardAction, - onTapApplicationLogoAction: controller.redirectToInboxAction, - onTapAvatarAction: (position) => controller.handleClickAvatarAction(context, position), - ); - } - }), - Expanded(child: Row(children: [ - Column(children: [ - _buildComposerButton(context), - Expanded(child: SizedBox( - width: ResponsiveUtils.defaultSizeMenu, - child: Obx(() { - if (controller.searchMailboxActivated.isTrue) { - return const SearchMailboxView( - backgroundColor: AppColor.colorBgDesktop - ); - } else { - return MailboxView(); - } - }) - )) - ]), - Expanded(child: Column(children: [ - const SpamReportBannerWebWidget(), - QuotasBannerWidget( - margin: const EdgeInsetsDirectional.only(end: 16, top: 8), - ), - _buildVacationNotificationMessage(context), - Obx(() { - final presentationMailbox = controller.selectedMailbox.value; - if (controller.isEmptyTrashBannerEnabledOnWeb(context, presentationMailbox)) { - return Padding( - padding: const EdgeInsetsDirectional.only( - top: BannerEmptyTrashStyles.webTopMargin, - end: BannerEmptyTrashStyles.webEndMargin - ), - child: BannerEmptyTrashWidget( - onTapAction: () => controller.emptyTrashAction(context) - ), + body: Container( + color: AppColor.colorBgDesktop, + child: Column(children: [ + Obx(() { + final accountId = controller.accountId.value; + if (accountId == null) { + return const SizedBox.shrink(); + } else { + return NavigationBarWidget( + avatarUserName: controller.sessionCurrent?.username.firstCharacter ?? '', + searchForm: SearchInputFormWidget(), + appGridController: controller.appGridDashboardController, + onShowAppDashboardAction: controller.showAppDashboardAction, + onTapApplicationLogoAction: controller.redirectToInboxAction, + onTapAvatarAction: (position) => controller.handleClickAvatarAction(context, position), + ); + } + }), + Expanded(child: Row(children: [ + Column(children: [ + _buildComposerButton(context), + Expanded(child: SizedBox( + width: ResponsiveUtils.defaultSizeMenu, + child: Obx(() { + if (controller.searchMailboxActivated.isTrue) { + return const SearchMailboxView( + backgroundColor: AppColor.colorBgDesktop ); } else { - return const SizedBox.shrink(); + return MailboxView(); } - }), - Obx(() { - final presentationMailbox = controller.selectedMailbox.value; - if (controller.isEmptySpamBannerEnabledOnWeb(context, presentationMailbox)) { - return Padding( - padding: const EdgeInsetsDirectional.only( - top: BannerDeleteAllSpamEmailsStyles.webTopMargin, - end: BannerDeleteAllSpamEmailsStyles.webEndMargin - ), - child: BannerDeleteAllSpamEmailsWidget( - onTapAction: () => controller.openDialogEmptySpamFolder(context) - ), - ); - } else { + }) + )) + ]), + Expanded(child: Column(children: [ + const SpamReportBannerWebWidget(), + QuotasBannerWidget( + margin: const EdgeInsetsDirectional.only(end: 16, top: 8), + ), + _buildVacationNotificationMessage(context), + Obx(() { + final presentationMailbox = controller.selectedMailbox.value; + if (controller.isEmptyTrashBannerEnabledOnWeb(context, presentationMailbox)) { + return Padding( + padding: const EdgeInsetsDirectional.only( + top: BannerEmptyTrashStyles.webTopMargin, + end: BannerEmptyTrashStyles.webEndMargin + ), + child: BannerEmptyTrashWidget( + onTapAction: () => controller.emptyTrashAction(context) + ), + ); + } else { + return const SizedBox.shrink(); + } + }), + Obx(() { + final presentationMailbox = controller.selectedMailbox.value; + if (controller.isEmptySpamBannerEnabledOnWeb(context, presentationMailbox)) { + return Padding( + padding: const EdgeInsetsDirectional.only( + top: BannerDeleteAllSpamEmailsStyles.webTopMargin, + end: BannerDeleteAllSpamEmailsStyles.webEndMargin + ), + child: BannerDeleteAllSpamEmailsWidget( + onTapAction: () => controller.openDialogEmptySpamFolder(context) + ), + ); + } else { + return const SizedBox.shrink(); + } + }), + Obx(() => RecoverDeletedMessageLoadingBannerWidget( + isLoading: controller.isRecoveringDeletedMessage.value, + horizontalLoadingWidget: horizontalLoadingWidget, + responsiveUtils: controller.responsiveUtils, + )), + _buildListButtonQuickSearchFilter(context), + _buildMarkAsMailboxReadLoading(context), + Expanded(child: Obx(() { + switch(controller.dashboardRoute.value) { + case DashboardRoutes.thread: + return _buildThreadViewForWebDesktop(context); + case DashboardRoutes.emailDetailed: + return const EmailView(); + default: return const SizedBox.shrink(); - } - }), - Obx(() => RecoverDeletedMessageLoadingBannerWidget( - isLoading: controller.isRecoveringDeletedMessage.value, - horizontalLoadingWidget: horizontalLoadingWidget, - responsiveUtils: controller.responsiveUtils, - )), - _buildListButtonQuickSearchFilter(context), - _buildMarkAsMailboxReadLoading(context), - Expanded(child: Obx(() { - switch(controller.dashboardRoute.value) { - case DashboardRoutes.thread: - return _buildThreadViewForWebDesktop(context); - case DashboardRoutes.emailDetailed: - return const EmailView(); - default: - return const SizedBox.shrink(); - } - })) - ])) + } + })) ])) - ]), - ), + ])) + ]), ), ), tabletLarge: Obx(() { @@ -196,8 +194,8 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { }), ), Obx(() => controller.composerOverlayState.value == ComposerOverlayState.active - ? const ComposerView() - : const SizedBox.shrink() + ? const ComposerView() + : const SizedBox.shrink() ), Obx(() => controller.searchMailboxActivated.value == true && !controller.responsiveUtils.isWebDesktop(context) ? const SearchMailboxView() @@ -500,70 +498,73 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { right: AppUtils.isDirectionRTL(context) ? 0 : 8, left: AppUtils.isDirectionRTL(context) ? 8 : 0, ), - child: InkWell( - onTap: () { - if (!filter.isTapOpenPopupMenu()) { - controller.selectQuickSearchFilterAction(filter); - } - }, - onTapDown: (detail) { - final screenSize = MediaQuery.of(context).size; - final offset = detail.globalPosition; - final position = RelativeRect.fromLTRB( - offset.dx, - offset.dy, - screenSize.width - offset.dx, - screenSize.height - offset.dy, - ); + child: Semantics( + button: true, + child: InkWell( + onTap: filter.isTapOpenPopupMenu() + ? null + : () => controller.selectQuickSearchFilterAction(filter), + onTapDown: !filter.isTapOpenPopupMenu() + ? null + : (detail) { + final screenSize = MediaQuery.of(context).size; + final offset = detail.globalPosition; + final position = RelativeRect.fromLTRB( + offset.dx, + offset.dy, + screenSize.width - offset.dx, + screenSize.height - offset.dy, + ); - switch(filter) { - case QuickSearchFilter.last7Days: - _openPopupMenuDateFilter(context, position); - break; - case QuickSearchFilter.sortBy: - _openPopupMenuSortFilter(context, position); - break; - default: - break; - } - }, - borderRadius: const BorderRadius.all(Radius.circular(10)), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: filter.getBackgroundColor(isFilterSelected: isFilterSelected)), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - SvgPicture.asset( - filter.getIcon(controller.imagePaths, isFilterSelected: isFilterSelected), - width: 16, - height: 16, - fit: BoxFit.fill), - const SizedBox(width: 4), - Text( - filter == QuickSearchFilter.fromMe - ? _getQuickSearchFilterFromTitle(context) - : filter.getTitle( - context, - receiveTimeType: controller.searchController.receiveTimeFiltered, - startDate: controller.searchController.startDateFiltered, - endDate: controller.searchController.endDateFiltered, - sortOrderType: controller.searchController.sortOrderFiltered.value, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: filter.getTextStyle(isFilterSelected: isFilterSelected), - ), - if (filter == QuickSearchFilter.last7Days || filter == QuickSearchFilter.fromMe) - ... [ - const SizedBox(width: 4), - SvgPicture.asset( - controller.imagePaths.icChevronDown, - width: 16, - height: 16, - fit: BoxFit.fill), - ] - ])), + switch(filter) { + case QuickSearchFilter.last7Days: + _openPopupMenuDateFilter(context, position); + break; + case QuickSearchFilter.sortBy: + _openPopupMenuSortFilter(context, position); + break; + default: + break; + } + }, + borderRadius: const BorderRadius.all(Radius.circular(10)), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: filter.getBackgroundColor(isFilterSelected: isFilterSelected)), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + SvgPicture.asset( + filter.getIcon(controller.imagePaths, isFilterSelected: isFilterSelected), + width: 16, + height: 16, + fit: BoxFit.fill), + const SizedBox(width: 4), + Text( + filter == QuickSearchFilter.fromMe + ? _getQuickSearchFilterFromTitle(context) + : filter.getTitle( + context, + receiveTimeType: controller.searchController.receiveTimeFiltered, + startDate: controller.searchController.startDateFiltered, + endDate: controller.searchController.endDateFiltered, + sortOrderType: controller.searchController.sortOrderFiltered.value, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: filter.getTextStyle(isFilterSelected: isFilterSelected), + ), + if (filter == QuickSearchFilter.last7Days || filter == QuickSearchFilter.fromMe) + ... [ + const SizedBox(width: 4), + SvgPicture.asset( + controller.imagePaths.icChevronDown, + width: 16, + height: 16, + fit: BoxFit.fill), + ] + ])), + ), ), ); }); @@ -576,6 +577,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { ) { return EmailReceiveTimeType.values .map((receiveTime) => PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemNoIconWidget( receiveTime.getTitle(context), @@ -594,6 +596,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { ) { return EmailSortOrderType.values .map((sortType) => PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemNoIconWidget( sortType.getTitle(context), @@ -642,27 +645,31 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { ), width: ResponsiveUtils.defaultSizeMenu, alignment: Alignment.centerLeft, - child: TMailButtonWidget( - key: const Key('compose_email_button'), - text: AppLocalizations.of(context).compose, - icon: controller.imagePaths.icComposeWeb, - borderRadius: 10, - iconSize: 24, - iconColor: Colors.white, - padding: const EdgeInsetsDirectional.symmetric(vertical: 8), - backgroundColor: AppColor.colorTextButton, - boxShadow: const [ - BoxShadow( - blurRadius: 12.0, - color: AppColor.colorShadowComposerButton - ) - ], - textStyle: const TextStyle( - fontSize: 15, - color: Colors.white, - fontWeight: FontWeight.w500 + child: Semantics( + identifier: ComposerKeyValues.openComposerButton, + container: true, + child: TMailButtonWidget( + key: const Key('compose_email_button'), + text: AppLocalizations.of(context).compose, + icon: controller.imagePaths.icComposeWeb, + borderRadius: 10, + iconSize: 24, + iconColor: Colors.white, + padding: const EdgeInsetsDirectional.symmetric(vertical: 8), + backgroundColor: AppColor.colorTextButton, + boxShadow: const [ + BoxShadow( + blurRadius: 12.0, + color: AppColor.colorShadowComposerButton + ) + ], + textStyle: const TextStyle( + fontSize: 15, + color: Colors.white, + fontWeight: FontWeight.w500 + ), + onTapActionCallback: () => controller.goToComposer(ComposerArguments()), ), - onTapActionCallback: () => controller.goToComposer(ComposerArguments()), ), ); } diff --git a/lib/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart b/lib/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart index 056cbe43f9..e4cd4bb021 100644 --- a/lib/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart +++ b/lib/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart @@ -21,6 +21,7 @@ mixin FilterEmailPopupMenuMixin { if (!isSearchEmailRunning) ...[ PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: _filterEmailAction( context, @@ -30,6 +31,7 @@ mixin FilterEmailPopupMenuMixin { const PopupMenuDivider(height: 0.5) ], PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: _filterEmailAction( context, @@ -38,6 +40,7 @@ mixin FilterEmailPopupMenuMixin { onCallBack)), const PopupMenuDivider(height: 0.5), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: _filterEmailAction( context, diff --git a/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart b/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart index 08b5592cd6..ac65b0f189 100644 --- a/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart +++ b/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart @@ -43,6 +43,7 @@ mixin UserSettingPopupMenuMixin { ...[ const PopupMenuDivider(height: 0.5), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: _settingAction(context, onSettingAction) ), @@ -52,6 +53,7 @@ mixin UserSettingPopupMenuMixin { ...[ const PopupMenuDivider(height: 0.5), PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: _logoutAction(context, onLogoutAction) ), diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart index 9543849113..2ba17bcac0 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart @@ -1,10 +1,10 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/button/icon_button_web.dart'; -import 'package:core/presentation/views/checkbox/labeled_checkbox.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -29,7 +29,6 @@ class AdvancedSearchFilterFormBottomView extends GetWidget KeyboardListener( - focusNode: FocusNode(), - onKeyEvent: (event) { - if (event is KeyDownEvent && - event.logicalKey == LogicalKeyboardKey.tab) { - nextFocusNode?.requestFocus(); - } - }, - child: LabeledCheckbox( - label: AppLocalizations.of(context).hasAttachment, - focusNode: currentFocusNode, - contentPadding: EdgeInsets.zero, - value: controller.hasAttachment.value, - activeColor: AppColor.primaryColor, - onChanged: (value) => controller.hasAttachment.value = value ?? false, - ), - ), - ); + Widget _buildCheckboxHasAttachment({ + FocusNode? currentFocusNode, + FocusNode? nextFocusNode, + }) { + return Obx(() => CheckBoxHasAttachmentWidget( + hasAttachmentValue: controller.hasAttachment.value, + currentFocusNode: currentFocusNode, + nextFocusNode: nextFocusNode, + onChanged: (value) => controller.hasAttachment.value = value ?? false, + )); } Widget _buildButton({ diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart index e997a8c114..310a446eeb 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart @@ -11,32 +11,29 @@ class AdvancedSearchFilterOverlay extends StatelessWidget { @override Widget build(BuildContext context) { return PointerInterceptor( - child: GestureDetector( - onTap: () => FocusManager.instance.primaryFocus?.unfocus(), - child: Container( - constraints: BoxConstraints( - maxHeight: _getHeightOverlay(context), - ), - margin: const EdgeInsetsDirectional.only(top: 4, bottom: 16, end: 22), - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: const [ - BoxShadow( - color: AppColor.colorShadowComposer, - blurRadius: 32, - offset: Offset.zero), - BoxShadow( - color: AppColor.colorDropShadow, - blurRadius: 4, - offset: Offset.zero), - ] - ), - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), - child: AdvancedSearchInputForm(), - ), + child: Container( + constraints: BoxConstraints( + maxHeight: _getHeightOverlay(context), + ), + margin: const EdgeInsetsDirectional.only(top: 4, bottom: 16, end: 22), + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: const [ + BoxShadow( + color: AppColor.colorShadowComposer, + blurRadius: 32, + offset: Offset.zero), + BoxShadow( + color: AppColor.colorDropShadow, + blurRadius: 4, + offset: Offset.zero), + ] + ), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), + child: AdvancedSearchInputForm(), ), ), ); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_input_form.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_input_form.dart index 540b9e06f7..fea3288dbe 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_input_form.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_input_form.dart @@ -120,6 +120,7 @@ class AdvancedSearchInputForm extends GetWidget List _buildEmailReceiveTimeTypeActionTiles(BuildContext context) { return EmailReceiveTimeType.values .map((receiveTime) => PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: PopupItemNoIconWidget( receiveTime.getTitle(context), diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart index e786dde2be..92eae0a4cb 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart @@ -106,30 +106,33 @@ class DateDropDownButton extends StatelessWidget { ) { return DropdownMenuItem( value: receiveTime, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: 44, - child: Row(children: [ - Expanded(child: Text( - receiveTime.getTitle(context), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black - ), - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - )), - if (receiveTime == receiveTimeSelected) - SvgPicture.asset( - imagePaths.icChecked, - width: 20, - height: 20, - fit: BoxFit.fill - ) - ]), + child: Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: 44, + child: Row(children: [ + Expanded(child: Text( + receiveTime.getTitle(context), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black + ), + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + )), + if (receiveTime == receiveTimeSelected) + SvgPicture.asset( + imagePaths.icChecked, + width: 20, + height: 20, + fit: BoxFit.fill + ) + ]), + ), ), ), ); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart index 40318b6d18..b30ee7943c 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart @@ -38,9 +38,16 @@ class IconOpenAdvancedSearchWidget extends StatelessWidget { width: 16, height: 16), onTap: () { - log('IconOpenAdvancedSearchWidget::build(): clicked'); - advancedFilterController.initSearchFilterField(context); - searchController.openAdvanceSearch(); + log('IconOpenAdvancedSearchWidget::build:onTap: isAdvancedSearchViewOpen = ${searchController.isAdvancedSearchViewOpen}'); + log('IconOpenAdvancedSearchWidget::build:onTap: advancedSearchIsActivated = ${searchController.advancedSearchIsActivated}'); + FocusScope.of(context).unfocus(); + + if (searchController.isAdvancedSearchViewOpen.isTrue) { + searchController.closeAdvanceSearch(); + } else { + advancedFilterController.initSearchFilterField(context); + searchController.openAdvanceSearch(); + } }), ), ); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/sort_by_drop_down_button.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/sort_by_drop_down_button.dart index ef591f99f6..3f81aa441c 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/sort_by_drop_down_button.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/sort_by_drop_down_button.dart @@ -32,31 +32,34 @@ class SortByDropDownButton extends StatelessWidget { items: EmailSortOrderType.values .map((sortType) => DropdownMenuItem( value: sortType, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: SortByDropdownStyle.height, - child: Row( - children: [ - Expanded( - child: Text( - sortType.getTitle(context), - style: sortType.getTextStyle(isInDropdown: true), - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, + child: Semantics( + excludeSemantics: true, + child: PointerInterceptor( + child: Container( + color: Colors.transparent, + height: SortByDropdownStyle.height, + child: Row( + children: [ + Expanded( + child: Text( + sortType.getTitle(context), + style: sortType.getTextStyle(isInDropdown: true), + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + ), ), - ), - if (sortType == sortOrderSelected) - SvgPicture.asset( - imagePaths.icChecked, - width: SortByDropdownStyle.checkedIconSize, - height: SortByDropdownStyle.checkedIconSize, - fit: BoxFit.fill, - ) - ], - ), - ) + if (sortType == sortOrderSelected) + SvgPicture.asset( + imagePaths.icChecked, + width: SortByDropdownStyle.checkedIconSize, + height: SortByDropdownStyle.checkedIconSize, + fit: BoxFit.fill, + ) + ], + ), + ) + ), ) )).toList(), value: sortOrderSelected, diff --git a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart index 5d393bbedf..504b17f1bf 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart @@ -28,6 +28,7 @@ class AppGridDashboardIcon extends StatelessWidget { visible: isAppGridOpen, portalFollower: GestureDetector( behavior: HitTestBehavior.opaque, + excludeFromSemantics: true, onTap: appGridController.toggleAppGridDashboard ), child: PortalTarget( diff --git a/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart index d8463069d6..19fc8052c1 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart @@ -40,6 +40,7 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { portalFollower: PointerInterceptor( child: GestureDetector( behavior: HitTestBehavior.opaque, + excludeFromSemantics: true, onTap: _searchController.closeAdvanceSearch ), ), diff --git a/lib/features/manage_account/presentation/language_and_region/language_and_region_controller.dart b/lib/features/manage_account/presentation/language_and_region/language_and_region_controller.dart index 0de0089de9..7856ea389c 100644 --- a/lib/features/manage_account/presentation/language_and_region/language_and_region_controller.dart +++ b/lib/features/manage_account/presentation/language_and_region/language_and_region_controller.dart @@ -13,9 +13,7 @@ class LanguageAndRegionController extends BaseController { final SaveLanguageInteractor _saveLanguageInteractor; - final listSupportedLanguages = [].obs; final languageSelected = LocalizationService.defaultLocale.obs; - final isLanguageMenuOverlayOpen = RxBool(false); LanguageAndRegionController(this._saveLanguageInteractor); @@ -34,8 +32,6 @@ class LanguageAndRegionController extends BaseController { } void _setUpSupportedLanguages() { - listSupportedLanguages.value = LocalizationService.supportedLocales; - final currentLocale = Get.locale; log('LanguageAndRegionController::_setUpSupportedLanguages():currentLocale: $currentLocale'); if (currentLocale != null) { @@ -46,7 +42,6 @@ class LanguageAndRegionController extends BaseController { } void selectLanguage(Locale? selectedLocale) { - isLanguageMenuOverlayOpen.value = false; languageSelected.value = selectedLocale ?? LocalizationService.defaultLocale; _saveLanguage(languageSelected.value); } @@ -54,8 +49,4 @@ class LanguageAndRegionController extends BaseController { void _saveLanguage(Locale localeCurrent) { consumeState(_saveLanguageInteractor.execute(localeCurrent)); } - - void toggleLanguageMenuOverlay() { - isLanguageMenuOverlayOpen.toggle(); - } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/language_and_region/language_and_region_view.dart b/lib/features/manage_account/presentation/language_and_region/language_and_region_view.dart index 8bc230254f..5707858254 100644 --- a/lib/features/manage_account/presentation/language_and_region/language_and_region_view.dart +++ b/lib/features/manage_account/presentation/language_and_region/language_and_region_view.dart @@ -1,11 +1,13 @@ import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/views/responsive/responsive_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/language_and_region_controller.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/change_language_button_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/language_and_region_header_widget.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/language_menu_popup_dialog_widget.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/language_title_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings_utils.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; @@ -50,7 +52,42 @@ class LanguageAndRegionView extends GetWidget { children: [ const LanguageAndRegionHeaderWidget(), const SizedBox(height: 22), - Expanded(child: ChangeLanguageButtonWidget()) + Expanded( + child: LayoutBuilder(builder: (context, constraints) { + return ResponsiveWidget( + responsiveUtils: controller.responsiveUtils, + mobile: Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const LanguageTitleWidget(), + const SizedBox(height: 8), + Obx(() => LanguageMenuPopupDialogWidget( + languageSelected: controller.languageSelected.value, + maxWidth: constraints.maxWidth, + onSelectLanguageAction: controller.selectLanguage + )) + ] + ) + ), + desktop: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const LanguageTitleWidget(), + const SizedBox(height: 8), + SizedBox( + width: constraints.maxWidth / 2, + child: Obx(() => LanguageMenuPopupDialogWidget( + languageSelected: controller.languageSelected.value, + maxWidth: constraints.maxWidth / 2, + onSelectLanguageAction: controller.selectLanguage + )) + ), + ] + ) + ); + }) + ) ] ), ), diff --git a/lib/features/manage_account/presentation/language_and_region/widgets/change_language_button_widget.dart b/lib/features/manage_account/presentation/language_and_region/widgets/change_language_button_widget.dart deleted file mode 100644 index 3f29c3abc5..0000000000 --- a/lib/features/manage_account/presentation/language_and_region/widgets/change_language_button_widget.dart +++ /dev/null @@ -1,131 +0,0 @@ - -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/resources/image_paths.dart'; -import 'package:core/presentation/utils/responsive_utils.dart'; -import 'package:core/presentation/utils/style_utils.dart'; -import 'package:core/presentation/views/responsive/responsive_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/extensions/locale_extension.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/language_and_region_controller.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; - -class ChangeLanguageButtonWidget extends StatelessWidget { - - final _controller = Get.find(); - final _responsiveUtils = Get.find(); - final _imagePaths = Get.find(); - - ChangeLanguageButtonWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - return ResponsiveWidget( - responsiveUtils: _responsiveUtils, - mobile: Scaffold( - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTitleLanguageWidget(context), - const SizedBox(height: 8), - _buildLanguageMenu(context, constraints.maxWidth) - ] - ) - ), - desktop: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTitleLanguageWidget(context), - const SizedBox(height: 8), - SizedBox( - width: constraints.maxWidth / 2, - child: _buildLanguageMenu(context, constraints.maxWidth / 2) - ), - ] - ) - ); - }); - } - - Widget _buildTitleLanguageWidget(BuildContext context) { - return Text( - AppLocalizations.of(context).language, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: AppColor.colorContentEmail - ) - ); - } - - Widget _buildLanguageMenu(BuildContext context, double maxWidth) { - return Obx(() => PortalTarget( - visible: _controller.isLanguageMenuOverlayOpen.isTrue, - portalFollower: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => _controller.toggleLanguageMenuOverlay() - ), - child: PortalTarget( - anchor: const Aligned( - follower: Alignment.topRight, - target: Alignment.bottomRight, - widthFactor: 1, - backup: Aligned( - follower: Alignment.topRight, - target: Alignment.bottomRight, - widthFactor: 1, - ), - ), - portalFollower: Obx(() => LanguageRegionOverlay( - listSupportedLanguages: _controller.listSupportedLanguages, - localeSelected: _controller.languageSelected.value, - maxWidth: maxWidth, - onSelectLanguageAction: _controller.selectLanguage, - )), - visible: _controller.isLanguageMenuOverlayOpen.isTrue, - child: _buildDropDownMenuButton(context, maxWidth) - ) - )); - } - - Widget _buildDropDownMenuButton(BuildContext context, double maxWidth) { - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () => _controller.toggleLanguageMenuOverlay(), - borderRadius: const BorderRadius.all(Radius.circular(10)), - child: Container( - height: 44, - width: maxWidth, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: AppColor.colorInputBorderCreateMailbox, - width: 0.5, - ), - color: AppColor.colorItemSelected, - ), - padding: const EdgeInsetsDirectional.only(start: 12, end: 10), - child: Row(children: [ - Expanded(child: Text( - _controller.languageSelected.value.getLanguageNameByCurrentLocale(context), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black - ), - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - )), - SvgPicture.asset(_imagePaths.icDropDown) - ]), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart b/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart index ae01960779..82eadadf4d 100644 --- a/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart +++ b/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart @@ -29,10 +29,10 @@ class LanguageRegionOverlay extends StatelessWidget { width: maxWidth, margin: const EdgeInsets.only(top: 4, bottom: 24), padding: const EdgeInsets.all(8), - decoration: BoxDecoration( + decoration: const BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: const [ + borderRadius: BorderRadius.all(Radius.circular(12)), + boxShadow: [ BoxShadow( color: AppColor.colorShadowBgContentEmail, blurRadius: 24, diff --git a/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_popup_dialog_widget.dart b/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_popup_dialog_widget.dart new file mode 100644 index 0000000000..6a55219bf0 --- /dev/null +++ b/lib/features/manage_account/presentation/language_and_region/widgets/language_menu_popup_dialog_widget.dart @@ -0,0 +1,88 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/extensions/locale_extension.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/language_menu_overlay.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/widgets/lanuage_item_widget.dart'; +import 'package:tmail_ui_user/main/localizations/localization_service.dart'; + +class LanguageMenuPopupDialogWidget extends StatefulWidget { + + final Locale languageSelected; + final OnSelectLanguageAction onSelectLanguageAction; + final double maxWidth; + + const LanguageMenuPopupDialogWidget({ + super.key, + required this.languageSelected, + required this.maxWidth, + required this.onSelectLanguageAction, + }); + + @override + State createState() => _LanguageMenuPopupDialogWidgetState(); +} + +class _LanguageMenuPopupDialogWidgetState extends State { + + final ImagePaths _imagePaths = Get.find(); + + bool _visible = false; + + @override + Widget build(BuildContext context) { + return PortalTarget( + visible: _visible, + portalFollower: GestureDetector( + behavior: HitTestBehavior.opaque, + excludeFromSemantics: true, + onTap: () => setState(() => _visible = false) + ), + child: PortalTarget( + anchor: const Aligned( + follower: Alignment.topRight, + target: Alignment.bottomRight, + widthFactor: 1, + backup: Aligned( + follower: Alignment.topRight, + target: Alignment.bottomRight, + widthFactor: 1, + ), + ), + portalFollower: LanguageRegionOverlay( + listSupportedLanguages: LocalizationService.supportedLocales, + localeSelected: widget.languageSelected, + maxWidth: widget.maxWidth, + onSelectLanguageAction: (locale) { + setState(() => _visible = false); + widget.onSelectLanguageAction(locale); + }, + ), + visible: _visible, + child: TMailButtonWidget( + text: widget.languageSelected.getLanguageNameByCurrentLocale(context), + icon: _imagePaths.icDropDown, + textStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black + ), + maxWidth: widget.maxWidth, + iconAlignment: TextDirection.rtl, + borderRadius: 10, + expandedText: true, + backgroundColor: AppColor.colorItemSelected, + padding: const EdgeInsetsDirectional.all(10), + border: Border.all( + color: AppColor.colorInputBorderCreateMailbox, + width: 0.5, + ), + onTapActionCallback: () => setState(() => _visible = true) + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/language_and_region/widgets/language_title_widget.dart b/lib/features/manage_account/presentation/language_and_region/widgets/language_title_widget.dart new file mode 100644 index 0000000000..6d79b91b1a --- /dev/null +++ b/lib/features/manage_account/presentation/language_and_region/widgets/language_title_widget.dart @@ -0,0 +1,20 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class LanguageTitleWidget extends StatelessWidget { + const LanguageTitleWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Text( + AppLocalizations.of(context).language, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: AppColor.colorContentEmail + ) + ); + } +} diff --git a/lib/features/manage_account/presentation/manage_account_dashboard_view.dart b/lib/features/manage_account/presentation/manage_account_dashboard_view.dart index bfe29c01ab..11374852a2 100644 --- a/lib/features/manage_account/presentation/manage_account_dashboard_view.dart +++ b/lib/features/manage_account/presentation/manage_account_dashboard_view.dart @@ -1,6 +1,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/responsive/responsive_widget.dart'; +import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:model/model.dart'; @@ -31,7 +32,9 @@ class ManageAccountDashBoardView extends GetWidget FocusScope.of(context).unfocus(), + onTap: PlatformInfo.isMobile + ? FocusScope.of(context).unfocus + : null, child: ResponsiveWidget( responsiveUtils: controller.responsiveUtils, desktop: Column(children: [ diff --git a/lib/features/manage_account/presentation/vacation/vacation_view.dart b/lib/features/manage_account/presentation/vacation/vacation_view.dart index a27451ef6d..32d6b17890 100644 --- a/lib/features/manage_account/presentation/vacation/vacation_view.dart +++ b/lib/features/manage_account/presentation/vacation/vacation_view.dart @@ -278,7 +278,6 @@ class VacationView extends GetWidget with RichTextButtonMixi return SettingDetailViewBuilder( responsiveUtils: controller.responsiveUtils, padding: const EdgeInsets.symmetric(horizontal: 4), - onTapGestureDetector: () => controller.clearFocusEditor(context), child: vacationInputForm, ); } else { diff --git a/lib/features/search/email/presentation/search_email_view.dart b/lib/features/search/email/presentation/search_email_view.dart index 5ed097a539..769e325d8d 100644 --- a/lib/features/search/email/presentation/search_email_view.dart +++ b/lib/features/search/email/presentation/search_email_view.dart @@ -52,7 +52,9 @@ class SearchEmailView extends GetWidget return Scaffold( body: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), + onTap: PlatformInfo.isWeb + ? null + : FocusScope.of(context).unfocus, child: Container( color: Colors.white, child: Column(children: [ @@ -299,6 +301,7 @@ class SearchEmailView extends GetWidget ) { return EmailReceiveTimeType.values .map((timeType) => PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: EmailReceiveTimeActionTileWidget( receiveTimeSelected: receiveTimeSelected, @@ -341,6 +344,7 @@ class SearchEmailView extends GetWidget ) { return EmailSortOrderType.values .map((sortType) => PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: EmailSortByActionTitleWidget( sortType: sortType, @@ -664,6 +668,7 @@ class SearchEmailView extends GetWidget List _popupMenuActionTile(BuildContext context, PresentationEmail email) { return [ PopupMenuItem( + enabled: false, padding: const EdgeInsets.symmetric(horizontal: 8), child: _markAsEmailSpamOrUnSpamAction(context, email)), ]; diff --git a/lib/features/search/mailbox/presentation/search_mailbox_view.dart b/lib/features/search/mailbox/presentation/search_mailbox_view.dart index beeb620712..0dd27f8e22 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_view.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_view.dart @@ -34,32 +34,34 @@ class SearchMailboxView extends GetWidget @override Widget build(BuildContext context) { return Scaffold( - body: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), - child: PlatformInfo.isWeb - ? PointerInterceptor(child: _buildSearchBody(context)) - : SafeArea(child: _buildSearchBody(context)), - ), + body: PlatformInfo.isWeb + ? PointerInterceptor(child: _buildSearchBody(context)) + : GestureDetector( + onTap: FocusScope.of(context).unfocus, + child: SafeArea(child: _buildSearchBody(context))), ); } Widget _buildSearchBody(BuildContext context) { - return Container( - color: backgroundColor ?? Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Column(children: [ - Container( - color: Colors.transparent, - padding: SearchMailboxUtils.getPaddingAppBar(context, controller.responsiveUtils), - child: _buildSearchInputForm(context) - ), - if (!controller.responsiveUtils.isWebDesktop(context)) - const Divider(color: AppColor.colorDividerComposer, height: 1), - _buildLoadingView(), - Expanded( - child: _buildMailboxListView(context) - ) - ]), + return Semantics( + container: true, + child: Container( + color: backgroundColor ?? Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Column(children: [ + Container( + color: Colors.transparent, + padding: SearchMailboxUtils.getPaddingAppBar(context, controller.responsiveUtils), + child: _buildSearchInputForm(context) + ), + if (!controller.responsiveUtils.isWebDesktop(context)) + const Divider(color: AppColor.colorDividerComposer, height: 1), + _buildLoadingView(), + Expanded( + child: _buildMailboxListView(context) + ) + ]), + ), ); } diff --git a/lib/features/thread/presentation/mixin/base_email_item_tile.dart b/lib/features/thread/presentation/mixin/base_email_item_tile.dart index 493bac458d..0e383b562a 100644 --- a/lib/features/thread/presentation/mixin/base_email_item_tile.dart +++ b/lib/features/thread/presentation/mixin/base_email_item_tile.dart @@ -14,6 +14,7 @@ import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/presentation_email_extension.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/base/key_values/email_tile_key_values.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart'; @@ -85,29 +86,37 @@ mixin BaseEmailItemTile { SearchQuery? query ) { if (isSearchEnabled(isSearchEmailRunning, query)) { - return RichTextBuilder( - textOrigin: informationSender(email, mailbox), - wordToStyle: query?.value ?? '', - styleOrigin: TextStyle( - fontSize: 15, - color: buildTextColorForReadEmail(email), - fontWeight: buildFontForReadEmail(email) + return Semantics( + identifier: EmailTileKeyValues.sender, + container: true, + child: RichTextBuilder( + textOrigin: informationSender(email, mailbox), + wordToStyle: query?.value ?? '', + styleOrigin: TextStyle( + fontSize: 15, + color: buildTextColorForReadEmail(email), + fontWeight: buildFontForReadEmail(email) + ), + styleWord: TextStyle( + fontSize: 15, + color: buildTextColorForReadEmail(email), + backgroundColor: AppColor.bgWordSearch, + fontWeight: buildFontForReadEmail(email) + ) ), - styleWord: TextStyle( - fontSize: 15, - color: buildTextColorForReadEmail(email), - backgroundColor: AppColor.bgWordSearch, - fontWeight: buildFontForReadEmail(email) - ) ); } else { - return TextOverflowBuilder( - informationSender(email, mailbox), - style: TextStyle( - fontSize: 15, - overflow: CommonTextStyle.defaultTextOverFlow, - color: buildTextColorForReadEmail(email), - fontWeight: buildFontForReadEmail(email)) + return Semantics( + identifier: EmailTileKeyValues.sender, + container: true, + child: TextOverflowBuilder( + informationSender(email, mailbox), + style: TextStyle( + fontSize: 15, + overflow: CommonTextStyle.defaultTextOverFlow, + color: buildTextColorForReadEmail(email), + fontWeight: buildFontForReadEmail(email)) + ), ); } } @@ -152,27 +161,35 @@ mixin BaseEmailItemTile { SearchQuery? query ) { if (isSearchEnabled(isSearchEmailRunning, query)) { - return RichTextBuilder( - textOrigin: email.getPartialContent(), - wordToStyle: query?.value ?? '', - styleOrigin: const TextStyle( - fontSize: 13, - color: AppColor.colorContentEmail, - fontWeight: FontWeight.normal + return Semantics( + identifier: EmailTileKeyValues.preview, + container: true, + child: RichTextBuilder( + textOrigin: email.getPartialContent(), + wordToStyle: query?.value ?? '', + styleOrigin: const TextStyle( + fontSize: 13, + color: AppColor.colorContentEmail, + fontWeight: FontWeight.normal + ), + styleWord: const TextStyle( + fontSize: 13, + color: AppColor.colorContentEmail, + backgroundColor: AppColor.bgWordSearch + ) ), - styleWord: const TextStyle( - fontSize: 13, - color: AppColor.colorContentEmail, - backgroundColor: AppColor.bgWordSearch - ) ); } else { - return TextOverflowBuilder( - email.getPartialContent(), - style: const TextStyle( - fontSize: 13, - color: AppColor.colorContentEmail, - fontWeight: FontWeight.normal) + return Semantics( + identifier: EmailTileKeyValues.preview, + container: true, + child: TextOverflowBuilder( + email.getPartialContent(), + style: const TextStyle( + fontSize: 13, + color: AppColor.colorContentEmail, + fontWeight: FontWeight.normal) + ), ); } } diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index a56e8dcafd..7d3f7887ab 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/popup_menu_widget_mixin.dart'; import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart'; @@ -314,6 +315,7 @@ class ThreadView extends GetWidget ? EdgeInsets.only(bottom: controller.responsiveUtils.isTabletLarge(context) ? 85 : 70) : EdgeInsets.zero, child: ComposeFloatingButton( + key: const ValueKey(ComposerKeyValues.openComposerButton), scrollController: controller.listEmailController, onTap: () => controller.mailboxDashBoardController.goToComposer(ComposerArguments()) ), @@ -821,6 +823,7 @@ class ThreadView extends GetWidget PresentationMailbox? mailboxContain ) { return PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: popupItem( mailboxContain?.isSpam == true ? controller.imagePaths.icNotSpam : controller.imagePaths.icSpam, @@ -849,6 +852,7 @@ class ThreadView extends GetWidget PresentationMailbox? mailboxContain ) { return PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: popupItem( controller.imagePaths.icOpenInNewTab, @@ -872,6 +876,7 @@ class ThreadView extends GetWidget PresentationEmail email ) { return PopupMenuItem( + enabled: false, padding: EdgeInsets.zero, child: popupItem( controller.imagePaths.icMailboxArchived, diff --git a/lib/features/thread/presentation/widgets/email_tile_web_builder.dart b/lib/features/thread/presentation/widgets/email_tile_web_builder.dart index abb8241a4a..2910f36542 100644 --- a/lib/features/thread/presentation/widgets/email_tile_web_builder.dart +++ b/lib/features/thread/presentation/widgets/email_tile_web_builder.dart @@ -10,6 +10,7 @@ import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/select_mode.dart'; +import 'package:tmail_ui_user/features/base/key_values/email_tile_key_values.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/presentation/mixin/base_email_item_tile.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -317,149 +318,152 @@ class _EmailTileBuilderState extends State with BaseEmailItem ), ), ), - desktop: Padding( - padding: const EdgeInsetsDirectional.only( - top: 2, - start: 3, - end: 3, - ), - child: Material( - type: MaterialType.transparency, - child: InkWell( - onTap: () => widget.emailActionClick?.call( - EmailActionType.preview, - widget.presentationEmail - ), - onHover: (value) => _hoverNotifier.value = value, - hoverColor: AppColor.colorEmailTileHoverWeb, - borderRadius: const BorderRadius.all(Radius.circular(14)), - child: Container( - padding: widget.padding ?? _getPaddingItem(context), - decoration: _getDecorationItem(), - alignment: Alignment.center, - child: Row(children: [ - const SizedBox(width: 10), - buildIconWeb( - icon: ValueListenableBuilder( - valueListenable: _hoverNotifier, - builder: (context, isHovered, child) { - return SvgPicture.asset( - widget.presentationEmail.isSelected - ? imagePaths.icCheckboxSelected - : imagePaths.icCheckboxUnselected, - colorFilter: ColorFilter.mode( - isHovered || widget.presentationEmail.isSelected - ? AppColor.primaryColor - : AppColor.colorEmailTileCheckboxUnhover, - BlendMode.srcIn), - width: 20, - height: 20); + desktop: Semantics( + identifier: EmailTileKeyValues.emailTile, + child: Padding( + padding: const EdgeInsetsDirectional.only( + top: 2, + start: 3, + end: 3, + ), + child: Material( + type: MaterialType.transparency, + child: InkWell( + onTap: () => widget.emailActionClick?.call( + EmailActionType.preview, + widget.presentationEmail + ), + onHover: (value) => _hoverNotifier.value = value, + hoverColor: AppColor.colorEmailTileHoverWeb, + borderRadius: const BorderRadius.all(Radius.circular(14)), + child: Container( + padding: widget.padding ?? _getPaddingItem(context), + decoration: _getDecorationItem(), + alignment: Alignment.center, + child: Row(children: [ + const SizedBox(width: 10), + buildIconWeb( + icon: ValueListenableBuilder( + valueListenable: _hoverNotifier, + builder: (context, isHovered, child) { + return SvgPicture.asset( + widget.presentationEmail.isSelected + ? imagePaths.icCheckboxSelected + : imagePaths.icCheckboxUnselected, + colorFilter: ColorFilter.mode( + isHovered || widget.presentationEmail.isSelected + ? AppColor.primaryColor + : AppColor.colorEmailTileCheckboxUnhover, + BlendMode.srcIn), + width: 20, + height: 20); + }, + ), + margin: const EdgeInsets.symmetric(vertical: 6), + iconPadding: EdgeInsets.zero, + minSize: 28, + tooltip: widget.presentationEmail.isSelected + ? AppLocalizations.of(context).selected + : AppLocalizations.of(context).notSelected, + onTap: () { + widget.emailActionClick?.call( + EmailActionType.selection, + widget.presentationEmail + ); }, ), - margin: const EdgeInsets.symmetric(vertical: 6), - iconPadding: EdgeInsets.zero, - minSize: 28, - tooltip: widget.presentationEmail.isSelected - ? AppLocalizations.of(context).selected - : AppLocalizations.of(context).notSelected, - onTap: () { - widget.emailActionClick?.call( - EmailActionType.selection, + buildIconWeb( + icon: SvgPicture.asset( + widget.presentationEmail.hasStarred + ? imagePaths.icStar + : imagePaths.icUnStar, + width: 20, + height: 20, + fit: BoxFit.fill + ), + margin: const EdgeInsets.symmetric(vertical: 6), + iconPadding: EdgeInsets.zero, + minSize: 28, + tooltip: widget.presentationEmail.hasStarred + ? AppLocalizations.of(context).starred + : AppLocalizations.of(context).not_starred, + onTap: () => widget.emailActionClick?.call( + widget.presentationEmail.hasStarred + ? EmailActionType.unMarkAsStarred + : EmailActionType.markAsStarred, widget.presentationEmail - ); - }, - ), - buildIconWeb( - icon: SvgPicture.asset( - widget.presentationEmail.hasStarred - ? imagePaths.icStar - : imagePaths.icUnStar, - width: 20, - height: 20, - fit: BoxFit.fill + ) ), - margin: const EdgeInsets.symmetric(vertical: 6), - iconPadding: EdgeInsets.zero, - minSize: 28, - tooltip: widget.presentationEmail.hasStarred - ? AppLocalizations.of(context).starred - : AppLocalizations.of(context).not_starred, - onTap: () => widget.emailActionClick?.call( - widget.presentationEmail.hasStarred - ? EmailActionType.unMarkAsStarred - : EmailActionType.markAsStarred, - widget.presentationEmail - ) - ), - buildIconWeb( - icon: buildIconAnsweredOrForwarded(presentationEmail: widget.presentationEmail), - tooltip: messageToolTipForAnsweredOrForwarded(context, widget.presentationEmail), - margin: const EdgeInsets.symmetric(vertical: 6), - iconPadding: EdgeInsets.zero, - minSize: 28, - splashRadius: 1 - ), - buildIconWeb( - icon: widget.presentationEmail.hasRead - ? const SizedBox(width: 20, height: 20) - : Container( - alignment: Alignment.center, - width: 20, - height: 20, - child: SvgPicture.asset( - imagePaths.icUnreadStatus, - width: 9, - height: 9, - fit: BoxFit.fill + buildIconWeb( + icon: buildIconAnsweredOrForwarded(presentationEmail: widget.presentationEmail), + tooltip: messageToolTipForAnsweredOrForwarded(context, widget.presentationEmail), + margin: const EdgeInsets.symmetric(vertical: 6), + iconPadding: EdgeInsets.zero, + minSize: 28, + splashRadius: 1 + ), + buildIconWeb( + icon: widget.presentationEmail.hasRead + ? const SizedBox(width: 20, height: 20) + : Container( + alignment: Alignment.center, + width: 20, + height: 20, + child: SvgPicture.asset( + imagePaths.icUnreadStatus, + width: 9, + height: 9, + fit: BoxFit.fill + ), ), - ), - margin: const EdgeInsets.symmetric(vertical: 6), - iconPadding: EdgeInsets.zero, - minSize: 28, - tooltip: widget.presentationEmail.hasRead - ? null - : AppLocalizations.of(context).mark_as_read, - onTap: widget.presentationEmail.hasRead ? null : () { - widget.emailActionClick?.call( - EmailActionType.markAsRead, - widget.presentationEmail - ); - }, - ), - buildIconAvatarText( - widget.presentationEmail, - iconSize: 32, - textStyle: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Colors.white + margin: const EdgeInsets.symmetric(vertical: 6), + iconPadding: EdgeInsets.zero, + minSize: 28, + tooltip: widget.presentationEmail.hasRead + ? null + : AppLocalizations.of(context).mark_as_read, + onTap: widget.presentationEmail.hasRead ? null : () { + widget.emailActionClick?.call( + EmailActionType.markAsRead, + widget.presentationEmail + ); + }, ), - ), - const SizedBox(width: 10), - SizedBox( - width: 160, - child: buildInformationSender( - context, + buildIconAvatarText( widget.presentationEmail, - widget.mailboxContain, - widget.isSearchEmailRunning, - widget.searchQuery - ) - ), - const SizedBox(width: 24), - Expanded(child: _buildSubjectAndContent()), - const SizedBox(width: 16), - ValueListenableBuilder( - valueListenable: _hoverNotifier, - builder: (context, value, child) { - if (value) { - return _buildListActionButtonWhenHover(context); - } else { - return _buildDateTimeForDesktopScreen(context); + iconSize: 32, + textStyle: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.white + ), + ), + const SizedBox(width: 10), + SizedBox( + width: 160, + child: buildInformationSender( + context, + widget.presentationEmail, + widget.mailboxContain, + widget.isSearchEmailRunning, + widget.searchQuery + ) + ), + const SizedBox(width: 24), + Expanded(child: _buildSubjectAndContent()), + const SizedBox(width: 16), + ValueListenableBuilder( + valueListenable: _hoverNotifier, + builder: (context, value, child) { + if (value) { + return _buildListActionButtonWhenHover(context); + } else { + return _buildDateTimeForDesktopScreen(context); + } } - } - ), - ]), + ), + ]), + ), ), ), ), @@ -615,11 +619,15 @@ class _EmailTileBuilderState extends State with BaseEmailItem Container( constraints: BoxConstraints(maxWidth: constraints.maxWidth / 2), padding: const EdgeInsetsDirectional.only(end: 12), - child: buildEmailTitle( - context, - widget.presentationEmail, - widget.isSearchEmailRunning, - widget.searchQuery + child: Semantics( + identifier: EmailTileKeyValues.subject, + container: true, + child: buildEmailTitle( + context, + widget.presentationEmail, + widget.isSearchEmailRunning, + widget.searchQuery + ), )), Expanded(child: Container( child: buildEmailPartialContent( diff --git a/lib/main.dart b/lib/main.dart index 86435990a5..611de84ab1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/utils/theme_utils.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/build_utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; @@ -18,6 +19,7 @@ import 'package:worker_manager/worker_manager.dart'; void main() async { initLogger(() async { WidgetsFlutterBinding.ensureInitialized(); + SemanticsBinding.instance.ensureSemantics(); ThemeUtils.setSystemLightUIStyle(); await Future.wait([ diff --git a/lib/main/pages/app_pages.dart b/lib/main/pages/app_pages.dart index 83e23838f0..5cd2c01ca1 100644 --- a/lib/main/pages/app_pages.dart +++ b/lib/main/pages/app_pages.dart @@ -1,8 +1,11 @@ import 'dart:io'; import 'package:core/core.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get/get_navigation/src/routes/get_route.dart'; +import 'package:tmail_ui_user/features/base/key_values/composer_key_values.dart'; +import 'package:tmail_ui_user/features/base/key_values/login_key_values.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_bindings.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart' deferred as composer; import 'package:tmail_ui_user/features/contact/presentation/contact_bindings.dart'; @@ -43,7 +46,9 @@ class AppPages { binding: HomeBindings()), GetPage( name: AppRoutes.login, - page: () => DeferredWidget(login.loadLibrary, () => login.LoginView()), + page: () => DeferredWidget(login.loadLibrary, () => login.LoginView( + key: const ValueKey(LoginKeyValues.loginView), + )), binding: LoginBindings()), GetPage( name: AppRoutes.dashboard, @@ -87,7 +92,7 @@ class AppPages { name: AppRoutes.composer, page: () => DeferredWidget( composer.loadLibrary, - () => composer.ComposerView()), + () => composer.ComposerView(key: const ValueKey(ComposerKeyValues.composerView))), binding: ComposerBindings()), GetPage( name: AppRoutes.destinationPicker, diff --git a/model/pubspec.lock b/model/pubspec.lock index d93d870fac..43edf83b7f 100644 --- a/model/pubspec.lock +++ b/model/pubspec.lock @@ -21,18 +21,18 @@ packages: dependency: transitive description: name: archive - sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265 + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.5.1" + version: "3.6.1" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: bidi - sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63" + sha256: "9a712c7ddf708f7c41b1923aa83648a3ed44cfd75b04f72d598c45e5be287f9d" url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.0.12" boolean_selector: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -85,34 +85,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.12" build_runner_core: - dependency: transitive + dependency: "direct overridden" description: name: build_runner_core - sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" url: "https://pub.dev" source: hosted - version: "7.2.7+1" + version: "7.2.9" built_collection: dependency: transitive description: @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.9.2" characters: dependency: transitive description: @@ -165,18 +165,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.10.0" collection: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -196,18 +196,18 @@ packages: dependency: transitive description: name: cross_file - sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.3+8" + version: "0.3.4+2" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" csslib: dependency: transitive description: @@ -244,18 +244,18 @@ packages: dependency: transitive description: name: device_info_plus - sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95" + sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "9.1.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" dio: dependency: transitive description: @@ -284,18 +284,18 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.3" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: @@ -324,10 +324,10 @@ packages: dependency: transitive description: name: flex_seed_scheme - sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f" + sha256: "4cee2f1d07259f77e8b36f4ec5f35499d19e74e17c7dce5b819554914082bc01" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" flutter: dependency: "direct main" description: flutter @@ -385,34 +385,34 @@ packages: dependency: transitive description: name: flutter_image_compress_common - sha256: "7cad12802628706655920089cfe9ee1d1098300e7f39a079eb160458bbc47652" + sha256: "7f79bc6c8a363063620b4e372fa86bc691e1cb28e58048cd38e030692fbd99ee" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.5" flutter_image_compress_macos: dependency: transitive description: name: flutter_image_compress_macos - sha256: fea1e3d71150d03373916b832c49b5c2f56c3e7e13da82a929274a2c6f88251e + sha256: "26df6385512e92b3789dc76b613b54b55c457a7f1532e59078b04bf189782d47" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" flutter_image_compress_platform_interface: dependency: transitive description: name: flutter_image_compress_platform_interface - sha256: eb4f055138b29b04498ebcb6d569aaaee34b64d75fb74ea0d40f9790bf47ee9d + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.5" flutter_image_compress_web: dependency: transitive description: name: flutter_image_compress_web - sha256: da41cc3859f19d11c7d10be615f6a9dcf0907e7daffde7442bf4cc2486663660 + sha256: f02fe352b17f82b72f481de45add240db062a2585850bea1667e82cc4cd6c311 url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.4+1" flutter_inappwebview: dependency: transitive description: @@ -555,10 +555,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" get: dependency: transitive description: @@ -579,10 +579,10 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" html: dependency: transitive description: @@ -595,10 +595,10 @@ packages: dependency: transitive description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -619,10 +619,10 @@ packages: dependency: transitive description: name: image - sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "4.1.7" + version: "4.2.0" intl: dependency: "direct main" description: @@ -660,10 +660,10 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: @@ -685,18 +685,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -741,18 +741,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: "direct main" description: @@ -813,18 +813,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -837,26 +837,26 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" pdf: dependency: transitive description: name: pdf - sha256: "243f05342fc0bdf140eba5b069398985cdbdd3dbb1d776cf43d5ea29cc570ba6" + sha256: "05df53f8791587402493ac97b9869d3824eccbc77d97855f4545cf72df3cae07" url: "https://pub.dev" source: hosted - version: "3.10.8" + version: "3.11.1" pdf_widget_wrapper: dependency: transitive description: @@ -869,58 +869,58 @@ packages: dependency: transitive description: name: petitparser - sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" platform: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.8" pointer_interceptor: dependency: transitive description: name: pointer_interceptor - sha256: bd18321519718678d5fa98ad3a3359cbc7a31f018554eab80b73d08a7f0c165a + sha256: d0a8e660d1204eaec5bd34b34cc92174690e076d2e4f893d9d68c486a13b07c4 url: "https://pub.dev" source: hosted - version: "0.10.1" + version: "0.10.1+1" pointer_interceptor_ios: dependency: transitive description: name: pointer_interceptor_ios - sha256: "4282ebfe21b54e21e26ab982c6086f0a67dc63423026bfba8db39a2e22045f26" + sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917 url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.10.1" pointer_interceptor_platform_interface: dependency: transitive description: name: pointer_interceptor_platform_interface - sha256: "59a446ead3be360bde72c3725f5ecacbba203c8a760e3061024c20f7da53f825" + sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506" url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.10.0+1" pointer_interceptor_web: dependency: transitive description: name: pointer_interceptor_web - sha256: dfd32b9c6e01a18f80535e7791e9d3add009a3395645d6db2f1e22b2642bfab4 + sha256: "7a7087782110f8c1827170660b09f8aa893e0e9a61431dbbe2ac3fc482e8c044" url: "https://pub.dev" source: hosted - version: "0.10.1" + version: "0.10.2+1" pool: dependency: transitive description: @@ -949,18 +949,18 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" qr: dependency: transitive description: name: qr - sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" quiver: dependency: "direct main" description: @@ -981,10 +981,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -994,10 +994,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_helper: dependency: transitive description: @@ -1042,10 +1042,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -1058,10 +1058,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" throttling: dependency: transitive description: @@ -1122,82 +1122,82 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.10" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1210,10 +1210,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: @@ -1226,42 +1226,58 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" win32: - dependency: transitive + dependency: "direct overridden" description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + url: "https://pub.dev" + source: hosted + version: "5.5.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "1.1.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" xml: dependency: transitive description: name: xml - sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.4.2" + version: "6.5.0" yaml: dependency: transitive description: @@ -1271,5 +1287,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.20.0-7.0.pre.48" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/model/pubspec.yaml b/model/pubspec.yaml index 5c0a75a1e8..0e1a5b8980 100644 --- a/model/pubspec.yaml +++ b/model/pubspec.yaml @@ -52,7 +52,7 @@ dependencies: mime: 1.0.4 - collection: 1.18.0 + collection: 1.19.0 dartz: 0.10.1 @@ -68,6 +68,12 @@ dev_dependencies: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec +dependency_overrides: + build_runner: 2.4.12 + build_runner_core: 7.2.9 + json_annotation: 4.8.1 + win32: 5.5.4 + flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in diff --git a/pubspec.lock b/pubspec.lock index 801f9dc384..fb647b3f75 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,18 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "64fcb0dbca4386356386c085142fa6e79c00a3326ceaa778a2d25f5d9ba61441" + sha256: "9371d13b8ee442e3bfc08a24e3a1b3742c839abbfaf5eef11b79c4b862c89bf7" url: "https://pub.dev" source: hosted - version: "1.0.16" + version: "1.3.41" + a_bridge: + dependency: transitive + description: + name: a_bridge + sha256: "5acd8dd8ba187576533aa93cd27c56695b9400ad282a89ce0b23f5b998fc78f3" + url: "https://pub.dev" + source: hosted + version: "0.0.3" analyzer: dependency: transitive description: @@ -33,22 +41,30 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" + appium_flutter_server: + dependency: "direct dev" + description: + name: appium_flutter_server + sha256: "05f07b7d71314906a93848950907961e0fca8e6ddd23adaac9845efdb28391ed" + url: "https://pub.dev" + source: hosted + version: "0.0.26" archive: dependency: transitive description: name: archive - sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.4.9" + version: "3.6.1" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: "direct main" description: @@ -85,10 +101,10 @@ packages: dependency: transitive description: name: bidi - sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63" + sha256: "9a712c7ddf708f7c41b1923aa83648a3ed44cfd75b04f72d598c45e5be287f9d" url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.0.12" boolean_selector: dependency: transitive description: @@ -101,10 +117,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -117,34 +133,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.12" build_runner_core: - dependency: transitive + dependency: "direct overridden" description: name: build_runner_core - sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" url: "https://pub.dev" source: hosted - version: "7.2.7+1" + version: "7.2.9" built_collection: dependency: "direct main" description: @@ -157,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.9.2" byte_converter: dependency: "direct main" description: @@ -213,18 +229,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.10.0" collection: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" connectivity_plus: dependency: "direct main" description: @@ -276,18 +292,18 @@ packages: dependency: transitive description: name: cross_file - sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.3+8" + version: "0.3.4+2" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" csslib: dependency: transitive description: @@ -380,18 +396,18 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95" + sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "9.1.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" dio: dependency: "direct main" description: @@ -445,10 +461,10 @@ packages: dependency: transitive description: name: enough_platform_widgets - sha256: c0defcb43452ba5df1058567fd6b920bf49f3fb6e6c57ee822a091f084cdee5f + sha256: "48ffc986a5453461812713957f32426033eca54fef03b5215b631d7c1736098f" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" equatable: dependency: "direct main" description: @@ -461,10 +477,10 @@ packages: dependency: "direct main" description: name: extended_text - sha256: d6036da79bcfa85c268ab92fc2c610b3ff2f2e3ff416ad0c3513cbb130fb36b6 + sha256: a09e3db6b405457a5cd43d19e892dcb7ec9ae1f0b40d5e95d06fedc2d1c13bf7 url: "https://pub.dev" source: hosted - version: "13.0.2" + version: "14.0.1" extended_text_library: dependency: transitive description: @@ -516,27 +532,58 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.3" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" file_picker: dependency: "direct main" description: - path: "." - ref: "email_supported_5.3.1" - resolved-ref: "6abb5a30d33ed04413bbc0273bfa946c379b28f0" - url: "https://github.com/linagora/flutter_file_picker" - source: git - version: "5.3.1" + name: file_picker + sha256: "825aec673606875c33cd8d3c4083f1a3c3999015a84178b317b7ef396b7384f3" + url: "https://pub.dev" + source: hosted + version: "8.0.7" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" filesize: dependency: "direct main" description: @@ -549,10 +596,10 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: fe30ac230f12f8836bb97e6e09197340d3c584526825b1746ea362a82e1e43f7 + sha256: "06537da27db981947fa535bb91ca120b4e9cb59cb87278dbdde718558cafc9ff" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "3.4.0" firebase_core_platform_interface: dependency: "direct overridden" description: @@ -565,34 +612,34 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: e8c408923cd3a25bd342c576a114f2126769cd1a57106a4edeaa67ea4a84e962 + sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88" url: "https://pub.dev" source: hosted - version: "2.8.0" + version: "2.17.5" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: "95f7565b8e992d2188cdd8dc5612330f7c309485fe425d3f9844f18e90741e3e" + sha256: "29941ba5a3204d80656c0e52103369aa9a53edfd9ceae05a2bb3376f24fda453" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "15.1.0" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: c5e79e15d1018cafffea1a6e45249db0d6bc42dbe35178634c77488179880e79 + sha256: "26c5370d3a79b15c8032724a68a4741e28f63e1f1a45699c4f0a8ae740aadd72" url: "https://pub.dev" source: hosted - version: "4.2.14" + version: "4.5.43" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: cd0cfcab7a63282049dec95a9955e7a487b5e580162310d12a82a10c0c32c546 + sha256: "58276cd5d9e22a9320ef9e5bc358628920f770f93c91221f8b638e8346ed5df4" url: "https://pub.dev" source: hosted - version: "3.2.15" + version: "3.8.13" fixnum: dependency: transitive description: @@ -621,10 +668,10 @@ packages: dependency: transitive description: name: flex_seed_scheme - sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f" + sha256: "4cee2f1d07259f77e8b36f4ec5f35499d19e74e17c7dce5b819554914082bc01" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" flutter: dependency: "direct main" description: flutter @@ -728,6 +775,11 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_image_compress: dependency: "direct main" description: @@ -740,34 +792,34 @@ packages: dependency: transitive description: name: flutter_image_compress_common - sha256: "7cad12802628706655920089cfe9ee1d1098300e7f39a079eb160458bbc47652" + sha256: "7f79bc6c8a363063620b4e372fa86bc691e1cb28e58048cd38e030692fbd99ee" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.5" flutter_image_compress_macos: dependency: transitive description: name: flutter_image_compress_macos - sha256: fea1e3d71150d03373916b832c49b5c2f56c3e7e13da82a929274a2c6f88251e + sha256: "26df6385512e92b3789dc76b613b54b55c457a7f1532e59078b04bf189782d47" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" flutter_image_compress_platform_interface: dependency: transitive description: name: flutter_image_compress_platform_interface - sha256: eb4f055138b29b04498ebcb6d569aaaee34b64d75fb74ea0d40f9790bf47ee9d + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.5" flutter_image_compress_web: dependency: transitive description: name: flutter_image_compress_web - sha256: da41cc3859f19d11c7d10be615f6a9dcf0907e7daffde7442bf4cc2486663660 + sha256: f02fe352b17f82b72f481de45add240db062a2585850bea1667e82cc4cd6c311 url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.4+1" flutter_inappwebview: dependency: transitive description: @@ -900,26 +952,26 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: bb5cd63ff7c91d6efe452e41d0d0ae6348925c82eafd10ce170ef585ea04776e + sha256: c500d5d9e7e553f06b61877ca6b9c8b92c570a4c8db371038702e8ce57f8a50f url: "https://pub.dev" source: hosted - version: "16.2.0" + version: "17.2.2" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af url: "https://pub.dev" source: hosted - version: "4.0.0+1" + version: "4.0.1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7" + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" url: "https://pub.dev" source: hosted - version: "7.1.0" + version: "7.2.0" flutter_localizations: dependency: "direct main" description: flutter @@ -937,18 +989,18 @@ packages: dependency: transitive description: name: flutter_platform_widgets - sha256: "4970c211af1dad0a161e6379d04de2cace80283da0439f2f87d31a541f9b2b84" + sha256: c483c0591d845d2adb84e341a1cfb746f1a8a7aff4c72a5957772446020601f4 url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da + sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.0.22" flutter_portal: dependency: "direct main" description: @@ -969,34 +1021,34 @@ packages: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" + sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c + sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.2" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.2" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.1" flutter_secure_storage_windows: dependency: transitive description: @@ -1008,11 +1060,12 @@ packages: flutter_slidable: dependency: "direct main" description: - name: flutter_slidable - sha256: "673403d2eeef1f9e8483bd6d8d92aae73b1d8bd71f382bc3930f699c731bc27c" - url: "https://pub.dev" - source: hosted - version: "3.1.0" + path: "." + ref: master + resolved-ref: "3280106581fc8d54eae45f4a446f92cae36d7837" + url: "https://github.com/letsar/flutter_slidable.git" + source: git + version: "3.1.1" flutter_staggered_grid_view: dependency: "direct main" description: @@ -1074,18 +1127,23 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" frontend_server_client: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" future_loading_dialog: dependency: "direct main" description: @@ -1114,10 +1172,10 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" hive: dependency: "direct main" description: @@ -1134,6 +1192,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + url: "https://pub.dev" + source: hosted + version: "4.2.0" html: dependency: "direct main" description: @@ -1155,10 +1221,18 @@ packages: dependency: transitive description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.2" + http_methods: + dependency: transitive + description: + name: http_methods + sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566" + url: "https://pub.dev" + source: hosted + version: "1.1.1" http_mock_adapter: dependency: "direct dev" description: @@ -1187,10 +1261,74 @@ packages: dependency: transitive description: name: image - sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "4.1.3" + version: "4.2.0" + image_picker: + dependency: transitive + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85 + url: "https://pub.dev" + source: hosted + version: "0.8.12+13" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + url: "https://pub.dev" + source: hosted + version: "0.8.12" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" infinite_listview: dependency: transitive description: @@ -1199,6 +1337,11 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + integration_test: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" internet_connection_checker: dependency: "direct main" description: @@ -1252,10 +1395,10 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: @@ -1277,18 +1420,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -1333,18 +1476,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: "direct main" description: @@ -1353,6 +1496,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + mime_type: + dependency: transitive + description: + name: mime_type + sha256: d652b613e84dac1af28030a9fba82c0999be05b98163f9e18a0849c6e63838bb + url: "https://pub.dev" + source: hosted + version: "1.0.1" mockito: dependency: "direct dev" description: @@ -1444,18 +1595,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -1468,18 +1619,18 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" pattern_formatter: dependency: transitive description: @@ -1492,10 +1643,10 @@ packages: dependency: transitive description: name: pdf - sha256: "243f05342fc0bdf140eba5b069398985cdbdd3dbb1d776cf43d5ea29cc570ba6" + sha256: "05df53f8791587402493ac97b9869d3824eccbc77d97855f4545cf72df3cae07" url: "https://pub.dev" source: hosted - version: "3.10.8" + version: "3.11.1" pdf_render: dependency: "direct main" description: @@ -1573,10 +1724,10 @@ packages: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -1589,18 +1740,18 @@ packages: dependency: "direct main" description: name: pointer_interceptor - sha256: bd18321519718678d5fa98ad3a3359cbc7a31f018554eab80b73d08a7f0c165a + sha256: d0a8e660d1204eaec5bd34b34cc92174690e076d2e4f893d9d68c486a13b07c4 url: "https://pub.dev" source: hosted - version: "0.10.1" + version: "0.10.1+1" pointer_interceptor_ios: dependency: transitive description: name: pointer_interceptor_ios - sha256: "2e73c39452830adc4695757130676a39412a3b7f3c34e3f752791b5384770877" + sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917 url: "https://pub.dev" source: hosted - version: "0.10.0+2" + version: "0.10.1" pointer_interceptor_platform_interface: dependency: transitive description: @@ -1613,18 +1764,18 @@ packages: dependency: transitive description: name: pointer_interceptor_web - sha256: "9386e064097fd16419e935c23f08f35b58e6aaec155dd39bd6a003b88f9c14b4" + sha256: "7a7087782110f8c1827170660b09f8aa893e0e9a61431dbbe2ac3fc482e8c044" url: "https://pub.dev" source: hosted - version: "0.10.1+2" + version: "0.10.2+1" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.9.1" pool: dependency: transitive description: @@ -1645,10 +1796,10 @@ packages: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.2" pub_semver: dependency: transitive description: @@ -1661,18 +1812,18 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" qr: dependency: transitive description: name: qr - sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" quiver: dependency: transitive description: @@ -1733,10 +1884,10 @@ packages: dependency: transitive description: name: share_plus_platform_interface - sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 + sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.0" shared_preferences: dependency: "direct main" description: @@ -1749,50 +1900,50 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.5.2" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.4.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shelf: dependency: transitive description: @@ -1801,6 +1952,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + shelf_hotreload: + dependency: transitive + description: + name: shelf_hotreload + sha256: d7099618b18d3c63ba5272491c1812c306629495129ef9996115f0417902f963 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + shelf_plus: + dependency: transitive + description: + name: shelf_plus + sha256: "4dba816c3584bca28ee8b31de4d08199e48c0e54db8a2ad0e94f777dd7ab625c" + url: "https://pub.dev" + source: hosted + version: "1.9.2" + shelf_router: + dependency: transitive + description: + name: shelf_router + sha256: f5e5d492440a7fb165fe1e2e1a623f31f734d3370900070b2b1e0d0428d59864 + url: "https://pub.dev" + source: hosted + version: "1.1.4" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" shelf_web_socket: dependency: transitive description: @@ -1826,10 +2009,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_helper: dependency: transitive description: @@ -1874,10 +2057,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" super_tag_editor: dependency: "direct main" description: @@ -1886,22 +2069,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" syncfusion_flutter_core: dependency: transitive description: name: syncfusion_flutter_core - sha256: "87cbeee9b916e013eedbd43bf07f253c34dd5184c88325e5227434e0f8b89a68" + sha256: "85f96b7b06f32a60b19ab0bb8c32bf162a0474f2d94f4844384be1118e6b4954" url: "https://pub.dev" source: hosted - version: "26.1.40" + version: "26.2.11" syncfusion_flutter_datepicker: dependency: transitive description: name: syncfusion_flutter_datepicker - sha256: ebc10c86618a8c410f4707dc6ff354daccb13d5f8a65f5fffa69a07806e4e0dd + sha256: "4c4803408c5661393fab84d802137c6df0ef77c3ea8683b92d42a95efb47f149" + url: "https://pub.dev" + source: hosted + version: "26.2.11" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255 url: "https://pub.dev" source: hosted - version: "26.1.40" + version: "3.2.0" term_glyph: dependency: transitive description: @@ -1914,10 +2113,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" textfield_tags: dependency: "direct main" description: @@ -1946,10 +2145,10 @@ packages: dependency: transitive description: name: timezone - sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0" + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" url: "https://pub.dev" source: hosted - version: "0.9.2" + version: "0.9.4" timing: dependency: transitive description: @@ -2002,58 +2201,58 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.10" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" url_strategy: dependency: "direct main" description: @@ -2074,26 +2273,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -2114,10 +2313,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: @@ -2130,26 +2329,42 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.4.0" - win32: + version: "2.4.5" + webdriver: dependency: transitive + description: + name: webdriver + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + win32: + dependency: "direct overridden" description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "5.5.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" + url: "https://pub.dev" + source: hosted + version: "1.1.0" worker_manager: dependency: "direct main" description: @@ -2170,10 +2385,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" xml: dependency: "direct dev" description: @@ -2191,5 +2406,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.20.0-7.0.pre.48" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 41d36b3d71..61c435f8dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -136,7 +136,7 @@ dependencies: http_parser: 4.0.2 - collection: 1.18.0 + collection: 1.19.0 filesize: 2.0.1 @@ -148,7 +148,7 @@ dependencies: path_provider: 2.1.1 - device_info_plus: 8.1.0 + device_info_plus: 9.1.0 permission_handler: 10.2.0 @@ -156,7 +156,7 @@ dependencies: hive: 2.2.3 - pointer_interceptor: 0.10.1 + pointer_interceptor: 0.10.1+1 rxdart: 0.27.7 @@ -190,11 +190,11 @@ dependencies: url_launcher: 6.1.10 - firebase_core: 2.7.0 + firebase_core: 3.4.0 - firebase_messaging: 14.2.5 + firebase_messaging: 15.1.0 - flutter_local_notifications: 16.2.0 + flutter_local_notifications: 17.2.2 fading_edge_scrollview: 4.1.1 @@ -218,13 +218,16 @@ dependencies: flutter_keyboard_visibility: 6.0.0 - extended_text: 13.0.2 + extended_text: 14.0.1 date_format: 2.0.7 flutter_linkify: 6.0.0 - flutter_slidable: 3.1.0 + flutter_slidable: + git: + url: https://github.com/letsar/flutter_slidable.git + ref: master url_strategy: 0.2.0 @@ -252,6 +255,8 @@ dev_dependencies: flutter_test: sdk: flutter + appium_flutter_server: 0.0.26 + build_runner: 2.3.3 mockito: 5.4.4 @@ -273,12 +278,14 @@ dev_dependencies: dependency_overrides: firebase_core_platform_interface: 4.6.0 + build_runner: 2.4.12 + build_runner_core: 7.2.9 + json_annotation: 4.8.1 + win32: 5.5.4 + # TODO: replace when upstream PR is merged: # https://github.com/miguelpruivo/flutter_file_picker/pull/1482 - file_picker: - git: - url: https://github.com/linagora/flutter_file_picker - ref: email_supported_5.3.1 + file_picker: 8.0.7 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/rule_filter/pubspec.lock b/rule_filter/pubspec.lock index bf16ad9212..8b730e9bb4 100644 --- a/rule_filter/pubspec.lock +++ b/rule_filter/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -61,34 +61,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.2" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.12" build_runner_core: - dependency: transitive + dependency: "direct overridden" description: name: build_runner_core - sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" url: "https://pub.dev" source: hosted - version: "7.2.7+1" + version: "7.2.9" built_collection: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.9.2" characters: dependency: transitive description: @@ -133,18 +133,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.10.0" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" dart_style: dependency: transitive description: @@ -239,10 +239,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -255,10 +255,10 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" http_mock_adapter: dependency: "direct main" description: @@ -304,18 +304,18 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: "direct main" description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: @@ -328,18 +328,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -376,26 +376,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" mockito: dependency: "direct dev" description: @@ -440,10 +440,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" quiver: dependency: transitive description: @@ -464,10 +464,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -477,10 +477,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_helper: dependency: transitive description: @@ -525,10 +525,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -541,10 +541,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" timing: dependency: transitive description: @@ -573,10 +573,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: @@ -585,14 +585,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" yaml: dependency: transitive description: @@ -602,5 +618,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.4.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/rule_filter/pubspec.yaml b/rule_filter/pubspec.yaml index bb8c5c7e0f..131a691203 100644 --- a/rule_filter/pubspec.yaml +++ b/rule_filter/pubspec.yaml @@ -41,6 +41,11 @@ dev_dependencies: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec +dependency_overrides: + build_runner: 2.4.12 + build_runner_core: 7.2.9 + json_annotation: 4.8.1 + # The following section is specific to Flutter. flutter: uses-material-design: true diff --git a/scripts/prebuild.sh b/scripts/prebuild.sh index 5104c75d65..d3a9a4611d 100755 --- a/scripts/prebuild.sh +++ b/scripts/prebuild.sh @@ -9,31 +9,31 @@ flutter pub get && dart run build_runner build --delete-conflicting-outputs ## Install necessary pods # cd ../ios -# flutter pub get && pod install +# flutter pub upgrade && pod install cd ../model -flutter pub get && dart run build_runner build --delete-conflicting-outputs +flutter pub upgrade && dart run build_runner build --delete-conflicting-outputs cd ../contact -flutter pub get && dart run build_runner build --delete-conflicting-outputs +flutter pub upgrade && dart run build_runner build --delete-conflicting-outputs cd ../forward -flutter pub get && dart run build_runner build --delete-conflicting-outputs +flutter pub upgrade && dart run build_runner build --delete-conflicting-outputs cd ../rule_filter -flutter pub get && dart run build_runner build --delete-conflicting-outputs +flutter pub upgrade && dart run build_runner build --delete-conflicting-outputs cd ../fcm -flutter pub get && dart run build_runner build --delete-conflicting-outputs +flutter pub upgrade && dart run build_runner build --delete-conflicting-outputs cd ../email_recovery -flutter pub get && dart run build_runner build --delete-conflicting-outputs +flutter pub upgrade && dart run build_runner build --delete-conflicting-outputs cd ../server_settings -flutter pub get && dart run build_runner build --delete-conflicting-outputs +flutter pub upgrade && dart run build_runner build --delete-conflicting-outputs cd .. -flutter pub get \ +flutter pub upgrade \ && dart run build_runner build --delete-conflicting-outputs \ && dart run intl_generator:extract_to_arb --output-dir=./lib/l10n lib/main/localizations/app_localizations.dart \ && dart run intl_generator:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/main/localizations/app_localizations.dart lib/l10n/intl*.arb diff --git a/scripts/run-desktop-integration-tests.sh b/scripts/run-desktop-integration-tests.sh new file mode 100755 index 0000000000..53a5f4fa5b --- /dev/null +++ b/scripts/run-desktop-integration-tests.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# docker run -d -p 2023:80 --name tmail-web -v "$(pwd)/env.file:/usr/share/nginx/html/assets/env.file" tmail-web:integration-test +./mvnw test \ No newline at end of file diff --git a/scripts/setup-desktop-integration-tests.sh b/scripts/setup-desktop-integration-tests.sh new file mode 100755 index 0000000000..2defadd664 --- /dev/null +++ b/scripts/setup-desktop-integration-tests.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +echo "$FRONTEND_CONFIG" > env.file +mkdir -p src/main/resources +echo "$FRONTEND_CREDS" > src/main/resources/config.properties diff --git a/server_settings/pubspec.lock b/server_settings/pubspec.lock index bc63780517..3c3f272ffd 100644 --- a/server_settings/pubspec.lock +++ b/server_settings/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.2" build_resolvers: dependency: transitive description: @@ -77,18 +77,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.12" build_runner_core: - dependency: transitive + dependency: "direct overridden" description: name: build_runner_core - sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" url: "https://pub.dev" source: hosted - version: "7.2.7+1" + version: "7.2.9" built_collection: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -304,10 +304,10 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: @@ -320,18 +320,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -360,18 +360,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -509,10 +509,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -525,10 +525,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" timing: dependency: transitive description: @@ -557,10 +557,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: @@ -586,5 +586,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.4.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/server_settings/pubspec.yaml b/server_settings/pubspec.yaml index 4e70d55723..907ec9c2fe 100644 --- a/server_settings/pubspec.yaml +++ b/server_settings/pubspec.yaml @@ -38,6 +38,11 @@ dev_dependencies: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec +dependency_overrides: + build_runner: 2.4.12 + build_runner_core: 7.2.9 + json_annotation: 4.8.1 + # The following section is specific to Flutter packages. flutter: diff --git a/tmail_integration_test/.gitignore b/tmail_integration_test/.gitignore new file mode 100644 index 0000000000..1213b4caa8 --- /dev/null +++ b/tmail_integration_test/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/target/ \ No newline at end of file diff --git a/tmail_integration_test/.mvn/wrapper/maven-wrapper.properties b/tmail_integration_test/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..f95f1ee807 --- /dev/null +++ b/tmail_integration_test/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip diff --git a/tmail_integration_test/mvnw b/tmail_integration_test/mvnw new file mode 100755 index 0000000000..19529ddf8c --- /dev/null +++ b/tmail_integration_test/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/tmail_integration_test/mvnw.cmd b/tmail_integration_test/mvnw.cmd new file mode 100644 index 0000000000..249bdf3822 --- /dev/null +++ b/tmail_integration_test/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/tmail_integration_test/pom.xml b/tmail_integration_test/pom.xml new file mode 100644 index 0000000000..e7cff8bc20 --- /dev/null +++ b/tmail_integration_test/pom.xml @@ -0,0 +1,109 @@ + + + + 4.0.0 + + com.linagora + tmail + 0.0.1-SNAPSHOT + + tmail + + http://www.example.com + + + UTF-8 + 22 + 22 + + + + + org.junit.jupiter + junit-jupiter-api + 5.10.3 + + + org.junit.jupiter + junit-jupiter-engine + 5.10.3 + + + org.junit.jupiter + junit-jupiter-params + 5.10.3 + + + + com.microsoft.playwright + playwright + 1.45.0 + + + org.testcontainers + testcontainers + 1.20.0 + test + + + io.projectreactor + reactor-core + 3.6.8 + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + false + + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/tmail_integration_test/src/main/java/com/tmail/base/BaseScenario.java b/tmail_integration_test/src/main/java/com/tmail/base/BaseScenario.java new file mode 100644 index 0000000000..38794f1532 --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/base/BaseScenario.java @@ -0,0 +1,9 @@ +package com.tmail.base; + +import com.microsoft.playwright.Page; + +public abstract class BaseScenario { + public abstract void execute(Page page); + + public TestUtils testUtils = new TestUtils(); +} diff --git a/tmail_integration_test/src/main/java/com/tmail/base/CoreRobot.java b/tmail_integration_test/src/main/java/com/tmail/base/CoreRobot.java new file mode 100644 index 0000000000..efbb518388 --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/base/CoreRobot.java @@ -0,0 +1,11 @@ +package com.tmail.base; + +import com.microsoft.playwright.Page; + +public abstract class CoreRobot { + protected Page page; + + public CoreRobot(Page page) { + this.page = page; + } +} diff --git a/tmail_integration_test/src/main/java/com/tmail/base/CustomParameterResolver.java b/tmail_integration_test/src/main/java/com/tmail/base/CustomParameterResolver.java new file mode 100644 index 0000000000..30fc136b2c --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/base/CustomParameterResolver.java @@ -0,0 +1,70 @@ +package com.tmail.base; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Optional; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter; +import org.junit.jupiter.engine.extension.ExtensionRegistry; + +public class CustomParameterResolver implements BeforeEachMethodAdapter, ParameterResolver { + + private ParameterResolver parameterisedTestParameterResolver = null; + + @Override + public void invokeBeforeEachMethod(ExtensionContext context, ExtensionRegistry registry) + throws Throwable { + Optional resolverOptional = registry.getExtensions(ParameterResolver.class) + .stream() + .filter(parameterResolver -> parameterResolver.getClass().getName() + .contains("ParameterizedTestParameterResolver")) + .findFirst(); + if (!resolverOptional.isPresent()) { + throw new IllegalStateException( + "ParameterizedTestParameterResolver missed in the registry. Probably it's not a Parameterized Test"); + } else { + parameterisedTestParameterResolver = resolverOptional.get(); + } + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + if (isExecutedOnAfterOrBeforeMethod(parameterContext)) { + ParameterContext pContext = getMappedContext(parameterContext, extensionContext); + return parameterisedTestParameterResolver.supportsParameter(pContext, extensionContext); + } + return false; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return parameterisedTestParameterResolver.resolveParameter( + getMappedContext(parameterContext, extensionContext), extensionContext); + } + + private MappedParameterContext getMappedContext(ParameterContext parameterContext, + ExtensionContext extensionContext) { + return new MappedParameterContext( + parameterContext.getIndex(), + extensionContext.getRequiredTestMethod().getParameters()[parameterContext.getIndex()], + Optional.of(parameterContext.getTarget())); + } + + private boolean isExecutedOnAfterOrBeforeMethod(ParameterContext parameterContext) { + return Arrays.stream(parameterContext.getDeclaringExecutable().getDeclaredAnnotations()) + .anyMatch(this::isAfterEachOrBeforeEachAnnotation); + } + + private boolean isAfterEachOrBeforeEachAnnotation(Annotation annotation) { + return annotation.annotationType() == BeforeEach.class + || annotation.annotationType() == AfterEach.class; + } +} \ No newline at end of file diff --git a/tmail_integration_test/src/main/java/com/tmail/base/MappedParameterContext.java b/tmail_integration_test/src/main/java/com/tmail/base/MappedParameterContext.java new file mode 100644 index 0000000000..7d24b8915b --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/base/MappedParameterContext.java @@ -0,0 +1,53 @@ +package com.tmail.base; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.platform.commons.util.AnnotationUtils; + +public class MappedParameterContext implements ParameterContext { + + private final int index; + private final Parameter parameter; + private final Optional target; + + public MappedParameterContext(int index, Parameter parameter, + Optional target) { + this.index = index; + this.parameter = parameter; + this.target = target; + } + + @Override + public boolean isAnnotated(Class annotationType) { + return AnnotationUtils.isAnnotated(parameter, annotationType); + } + + @Override + public Optional findAnnotation(Class annotationType) { + return Optional.empty(); + } + + @Override + public List findRepeatableAnnotations(Class annotationType) { + return null; + } + + @Override + public int getIndex() { + return index; + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Optional getTarget() { + return target; + } +} diff --git a/tmail_integration_test/src/main/java/com/tmail/base/SupportedPlatform.java b/tmail_integration_test/src/main/java/com/tmail/base/SupportedPlatform.java new file mode 100644 index 0000000000..1bebc611bc --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/base/SupportedPlatform.java @@ -0,0 +1,6 @@ +package com.tmail.base; + +public enum SupportedPlatform { + CHROME, + FIREFOX +} diff --git a/tmail_integration_test/src/main/java/com/tmail/base/TestBase.java b/tmail_integration_test/src/main/java/com/tmail/base/TestBase.java new file mode 100644 index 0000000000..011a131195 --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/base/TestBase.java @@ -0,0 +1,95 @@ +package com.tmail.base; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Properties; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.Browser.NewContextOptions; +import com.microsoft.playwright.BrowserContext; +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Playwright; +import com.microsoft.playwright.Tracing; + +@ExtendWith(CustomParameterResolver.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class TestBase { + private Playwright playwright; + private Browser browser; + private BrowserContext browserContext; + private Page page; + + protected BaseScenario scenario; + protected Properties properties; + + private static Boolean runHeadlessTest = true; + + public TestBase() { + properties = new Properties(); + ClassLoader loader = getClass().getClassLoader(); + try { + properties.load(loader.getResourceAsStream("config.properties")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @BeforeAll + void setUpAll() { + playwright = Playwright.create(); + } + + @BeforeEach + public void setUp(SupportedPlatform supportedPlatform) { + browser = switch (supportedPlatform) { + case CHROME -> playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(runHeadlessTest)); + case FIREFOX -> playwright.firefox().launch(new BrowserType.LaunchOptions().setHeadless(runHeadlessTest)); + default -> throw new UnsupportedPlatformException(); + }; + + NewContextOptions contextOptions = new Browser.NewContextOptions() + .setViewportSize(1920, 1080); + browserContext = browser.newContext(contextOptions); + browserContext.tracing().start(new Tracing.StartOptions() + .setScreenshots(true) + .setSnapshots(true)); + + page = browserContext.newPage(); + } + + @AfterEach + public void tearDown() { + browserContext.tracing().stop(new Tracing.StopOptions() + .setPath(Paths.get("trace.zip"))); + page.close(); + browserContext.close(); + browser.close(); + } + + @AfterAll + void tearDownAll() { + playwright.close(); + } + + static Stream supportedPlatforms() { + return Arrays.asList(SupportedPlatform.values()).stream(); + } + + @ParameterizedTest + @MethodSource("supportedPlatforms") + public void testScenario(SupportedPlatform supportedPlatform) { + scenario.execute(page); + } +} diff --git a/tmail_integration_test/src/main/java/com/tmail/base/TestUtils.java b/tmail_integration_test/src/main/java/com/tmail/base/TestUtils.java new file mode 100644 index 0000000000..e09c20307c --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/base/TestUtils.java @@ -0,0 +1,9 @@ +package com.tmail.base; + +import com.microsoft.playwright.Page; + +public class TestUtils { + public void waitFor(int seconds, Page page) { + page.waitForTimeout(seconds * 1000); + } +} diff --git a/tmail_integration_test/src/main/java/com/tmail/base/UnsupportedPlatformException.java b/tmail_integration_test/src/main/java/com/tmail/base/UnsupportedPlatformException.java new file mode 100644 index 0000000000..90591ae33b --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/base/UnsupportedPlatformException.java @@ -0,0 +1,5 @@ +package com.tmail.base; + +public class UnsupportedPlatformException extends VirtualMachineError { + +} diff --git a/tmail_integration_test/src/main/java/com/tmail/robots/BasicAuthLoginRobot.java b/tmail_integration_test/src/main/java/com/tmail/robots/BasicAuthLoginRobot.java new file mode 100644 index 0000000000..5657d81f14 --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/robots/BasicAuthLoginRobot.java @@ -0,0 +1,26 @@ +package com.tmail.robots; + +import com.microsoft.playwright.Page; +import com.tmail.base.CoreRobot; + +public class BasicAuthLoginRobot extends CoreRobot { + + public BasicAuthLoginRobot(Page page) { + super(page); + } + + public void enterUsername(String username) { + page.locator("#email").fill(username); + } + + public void enterPassword(String password) { + page.locator("input[aria-label='password']").click(); + page.waitForTimeout(1000); + page.locator("input[aria-label='password']").fill(password); + } + + public void clickLogin() { + page.getByText("Sign in").last().click(); + } + +} diff --git a/tmail_integration_test/src/main/java/com/tmail/robots/ComposerRobot.java b/tmail_integration_test/src/main/java/com/tmail/robots/ComposerRobot.java new file mode 100644 index 0000000000..744a2335a1 --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/robots/ComposerRobot.java @@ -0,0 +1,42 @@ +package com.tmail.robots; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +import com.microsoft.playwright.FrameLocator; +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.Page; +import com.tmail.base.CoreRobot; + +public class ComposerRobot extends CoreRobot { + + public ComposerRobot(Page page) { + super(page); + } + + public void addReceipients(ArrayList recipients) { + Locator toFieldSelector = page.getByLabel("Composer:to").locator("input"); + toFieldSelector.click(); + for (String recipient : recipients) { + toFieldSelector.fill(recipient); + page.getByText(Pattern.compile("Composer:suggestion:" + recipient)).click(); + } + page.keyboard().press("Tab"); + page.waitForTimeout(1000); + } + + public void addSubject(String subject) { + page.getByLabel("Composer:subject").fill(subject); + page.keyboard().press("Tab"); + } + + public void addContent(String content) { + FrameLocator frameLocator = page.frameLocator("iFrame"); + frameLocator.locator(".note-editable").fill(content); + } + + public void clickSend() { + page.getByText("Send").click(); + } + +} diff --git a/tmail_integration_test/src/main/java/com/tmail/robots/HomeRobot.java b/tmail_integration_test/src/main/java/com/tmail/robots/HomeRobot.java new file mode 100644 index 0000000000..aa4deb8b0d --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/robots/HomeRobot.java @@ -0,0 +1,15 @@ +package com.tmail.robots; + +import com.microsoft.playwright.Page; +import com.tmail.base.CoreRobot; + +public class HomeRobot extends CoreRobot { + + public HomeRobot(Page page) { + super(page); + } + + public void navigateToTestSite(String url) { + page.navigate(url); + } +} diff --git a/tmail_integration_test/src/main/java/com/tmail/robots/MailboxDashboardRobot.java b/tmail_integration_test/src/main/java/com/tmail/robots/MailboxDashboardRobot.java new file mode 100644 index 0000000000..2eb9216389 --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/robots/MailboxDashboardRobot.java @@ -0,0 +1,24 @@ +package com.tmail.robots; + +import com.microsoft.playwright.Page; +import com.tmail.base.CoreRobot; + +public class MailboxDashboardRobot extends CoreRobot { + + public MailboxDashboardRobot(Page page) { + super(page); + } + + public void openComposer() { + page.getByText("Compose").click(); + } + + public void waitForSendEmailSuccessToast() { + page.getByText("Message has been sent successfully").waitFor(); + } + + public void waitUntilExactLabelIsVisible(String string) { + page.getByText(string).waitFor(); + } + +} diff --git a/tmail_integration_test/src/main/java/com/tmail/robots/OidcLoginRobot.java b/tmail_integration_test/src/main/java/com/tmail/robots/OidcLoginRobot.java new file mode 100644 index 0000000000..3cb34e7377 --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/robots/OidcLoginRobot.java @@ -0,0 +1,24 @@ +package com.tmail.robots; + +import com.microsoft.playwright.Page; +import com.tmail.base.CoreRobot; + +public class OidcLoginRobot extends CoreRobot { + + public OidcLoginRobot(Page page) { + super(page); + } + + public void enterUsername(String username) { + page.locator("#username").fill(username); + } + + public void enterPassword(String password) { + page.locator("#password").fill(password); + } + + public void clickLogin() { + page.locator("#kc-login").click(); + } + +} diff --git a/tmail_integration_test/src/main/java/com/tmail/scenarios/BasicAuthLoginScenario.java b/tmail_integration_test/src/main/java/com/tmail/scenarios/BasicAuthLoginScenario.java new file mode 100644 index 0000000000..36d90ea62c --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/scenarios/BasicAuthLoginScenario.java @@ -0,0 +1,35 @@ +package com.tmail.scenarios; + +import com.microsoft.playwright.Page; +import com.tmail.base.BaseScenario; +import com.tmail.robots.BasicAuthLoginRobot; +import com.tmail.robots.HomeRobot; +import com.tmail.robots.MailboxDashboardRobot; + +public class BasicAuthLoginScenario extends BaseScenario { + String testUrl; + String username; + String password; + + public BasicAuthLoginScenario(String testUrl, String username, String password) { + this.testUrl = testUrl; + this.username = username; + this.password = password; + } + + @Override + public void execute(Page page) { + HomeRobot homeRobot = new HomeRobot(page); + BasicAuthLoginRobot loginRobot = new BasicAuthLoginRobot(page); + MailboxDashboardRobot mailboxDashboardRobot = new MailboxDashboardRobot(page); + + homeRobot.navigateToTestSite(testUrl); + + loginRobot.enterUsername(username); + loginRobot.enterPassword(password); + loginRobot.clickLogin(); + + mailboxDashboardRobot.waitUntilExactLabelIsVisible("Compose"); + } + +} diff --git a/tmail_integration_test/src/main/java/com/tmail/scenarios/ComposeEmailScenario.java b/tmail_integration_test/src/main/java/com/tmail/scenarios/ComposeEmailScenario.java new file mode 100644 index 0000000000..fc1dc01b99 --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/scenarios/ComposeEmailScenario.java @@ -0,0 +1,42 @@ +package com.tmail.scenarios; + +import java.util.ArrayList; +import java.util.List; + +import com.microsoft.playwright.Page; +import com.tmail.base.BaseScenario; +import com.tmail.robots.ComposerRobot; +import com.tmail.robots.MailboxDashboardRobot; + +public class ComposeEmailScenario extends BaseScenario { + String testUrl; + String username; + String password; + String additionalReceipent; + + public ComposeEmailScenario(String testUrl, String username, String password, String additionalReceipent) { + this.testUrl = testUrl; + this.username = username; + this.password = password; + this.additionalReceipent = additionalReceipent; + } + + @Override + public void execute(Page page) { + MailboxDashboardRobot mailboxDashboardRobot = new MailboxDashboardRobot(page); + ComposerRobot composerRobot = new ComposerRobot(page); + + OidcLoginScenario loginUseCase = new OidcLoginScenario(testUrl, username, password); + loginUseCase.execute(page); + + mailboxDashboardRobot.openComposer(); + + composerRobot.addReceipients(new ArrayList(List.of(username, additionalReceipent))); + composerRobot.addSubject("Test subject"); + composerRobot.addContent("Test content"); + composerRobot.clickSend(); + + mailboxDashboardRobot.waitForSendEmailSuccessToast(); + } + +} diff --git a/tmail_integration_test/src/main/java/com/tmail/scenarios/OidcLoginScenario.java b/tmail_integration_test/src/main/java/com/tmail/scenarios/OidcLoginScenario.java new file mode 100644 index 0000000000..f79217d85e --- /dev/null +++ b/tmail_integration_test/src/main/java/com/tmail/scenarios/OidcLoginScenario.java @@ -0,0 +1,35 @@ +package com.tmail.scenarios; + +import com.microsoft.playwright.Page; +import com.tmail.base.BaseScenario; +import com.tmail.robots.HomeRobot; +import com.tmail.robots.MailboxDashboardRobot; +import com.tmail.robots.OidcLoginRobot; + +public class OidcLoginScenario extends BaseScenario { + String testUrl; + String username; + String password; + + public OidcLoginScenario(String testUrl, String username, String password) { + this.testUrl = testUrl; + this.username = username; + this.password = password; + } + + @Override + public void execute(Page page) { + HomeRobot homeRobot = new HomeRobot(page); + OidcLoginRobot loginRobot = new OidcLoginRobot(page); + MailboxDashboardRobot mailboxDashboardRobot = new MailboxDashboardRobot(page); + + homeRobot.navigateToTestSite(testUrl); + + loginRobot.enterUsername(username); + loginRobot.enterPassword(password); + loginRobot.clickLogin(); + + mailboxDashboardRobot.waitUntilExactLabelIsVisible("Compose"); + } + +} diff --git a/tmail_integration_test/src/test/java/com/tmail/preprod/basic_auth/login/BasicAuthLoginTest.java b/tmail_integration_test/src/test/java/com/tmail/preprod/basic_auth/login/BasicAuthLoginTest.java new file mode 100644 index 0000000000..baffbdef5d --- /dev/null +++ b/tmail_integration_test/src/test/java/com/tmail/preprod/basic_auth/login/BasicAuthLoginTest.java @@ -0,0 +1,24 @@ +package com.tmail.preprod.basic_auth.login; + +import static com.tmail.preprod.extension.Fixture.BOB; +import static com.tmail.preprod.extension.Fixture.PASSWORD; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.tmail.base.TestBase; +import com.tmail.preprod.extension.TmailExtension; +import com.tmail.scenarios.BasicAuthLoginScenario; + +public class BasicAuthLoginTest extends TestBase { + @RegisterExtension + TmailExtension tmailExtension = new TmailExtension(); + + @BeforeEach + void setupScenario() { + scenario = new BasicAuthLoginScenario( + tmailExtension.getTmailWebUrl().toString(), + BOB, + PASSWORD); + } +} diff --git a/tmail_integration_test/src/test/java/com/tmail/preprod/extension/Fixture.java b/tmail_integration_test/src/test/java/com/tmail/preprod/extension/Fixture.java new file mode 100644 index 0000000000..9c7fdfcd1d --- /dev/null +++ b/tmail_integration_test/src/test/java/com/tmail/preprod/extension/Fixture.java @@ -0,0 +1,7 @@ +package com.tmail.preprod.extension; + +public interface Fixture { + String DOMAIN = "domain.tld"; + String BOB = "bob@domain.tld"; + String PASSWORD = "password"; +} diff --git a/tmail_integration_test/src/test/java/com/tmail/preprod/extension/Runnables.java b/tmail_integration_test/src/test/java/com/tmail/preprod/extension/Runnables.java new file mode 100644 index 0000000000..b4070a19f0 --- /dev/null +++ b/tmail_integration_test/src/test/java/com/tmail/preprod/extension/Runnables.java @@ -0,0 +1,26 @@ +package com.tmail.preprod.extension; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +public class Runnables { + public static void runParallel(Runnable... runnables) { + Flux stream = Flux.just(runnables); + runParallel(stream); + } + + public static void runParallel(Flux runnables) { + runnables + .publishOn(Schedulers.boundedElastic()) + .parallel() + .runOn(Schedulers.boundedElastic()) + .flatMap(runnable -> { + runnable.run(); + return Mono.empty(); + }) + .sequential() + .then() + .block(); + } +} diff --git a/tmail_integration_test/src/test/java/com/tmail/preprod/extension/TmailExtension.java b/tmail_integration_test/src/test/java/com/tmail/preprod/extension/TmailExtension.java new file mode 100644 index 0000000000..fbf388f817 --- /dev/null +++ b/tmail_integration_test/src/test/java/com/tmail/preprod/extension/TmailExtension.java @@ -0,0 +1,332 @@ +package com.tmail.preprod.extension; + +import static com.tmail.preprod.extension.Fixture.BOB; +import static com.tmail.preprod.extension.Fixture.DOMAIN; +import static com.tmail.preprod.extension.Fixture.PASSWORD; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Optional; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.containers.wait.strategy.WaitStrategy; +import org.testcontainers.utility.MountableFile; + +public class TmailExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback { + + public enum IsolationLevel { + // Do not restart container after each test. Low isolation between tests, but fast pace for test suite. + // Suitable for either test suite that does not require strong isolation, or you are confident that backend data is well cleaned up each test. + // Note that in this mode, the TmailExtension already handles clean up some basic data after each test. You may need to clean up additional data yourself if needed. + LOW, + + // Always restart container after each test. Strong isolation between tests, but slow pace for test suite. + // Suitable for either test suite that requires strong isolation, or you are NOT confident that backend data is well cleaned up each test. + // Note that in this mode, you do not need to care about cleaning up data yourself. + HIGH + } + + private static final String TMAIL_WEB_LATEST_IMAGE_ENV = "tmail-web-latest-image"; // environment variable stores the docker image tag for the current branch. e.g., can set by CI or local dev + private static final WaitStrategy WAIT_STRATEGY = new LogMessageWaitStrategy().withRegEx(".*JAMES server started.*\\n").withTimes(1) + .withStartupTimeout(Duration.ofMinutes(3)); + private static final int JMAP_PORT = 80; + private static final int WEB_ADMIN_PORT = 8000; + private static final int NGINX_HTTP_PORT = 80; + private static final int TMAIL_WEB_PORT = 80; + + private final IsolationLevel isolationLevel; + private final GenericContainer tmailBackend; + private final GenericContainer tmailWeb; + private final GenericContainer nginx; + private final HttpClient httpClient; + + public TmailExtension(IsolationLevel isolationLevel) { + this.isolationLevel = isolationLevel; + + Network network = Network.newNetwork(); + + this.tmailBackend = new GenericContainer<>("linagora/tmail-backend:memory-0.10.2") + .withNetworkAliases("tmail-backend") + .withNetwork(network) + .withCopyFileToContainer(MountableFile.forClasspathResource("tmail-backend-conf/imapserver.xml"), "/root/conf/") + .withCopyFileToContainer(MountableFile.forClasspathResource("tmail-backend-conf/smtpserver.xml"), "/root/conf/") + .withCopyFileToContainer(MountableFile.forClasspathResource("tmail-backend-conf/jwt_privatekey"), "/root/conf/") + .withCopyFileToContainer(MountableFile.forClasspathResource("tmail-backend-conf/jwt_publickey"), "/root/conf/") + .withCopyFileToContainer(MountableFile.forClasspathResource("tmail-backend-conf/jmap.properties"), "/root/conf/") + .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withName("tmail-backend-memory-testing" + UUID.randomUUID())) + .waitingFor(WAIT_STRATEGY) + .withExposedPorts(JMAP_PORT, WEB_ADMIN_PORT); + + this.nginx = new GenericContainer<>("nginx:alpine") + .withNetwork(network) + .dependsOn(tmailBackend) + .withExposedPorts(NGINX_HTTP_PORT) + .waitingFor(Wait.forHttp("/").forStatusCode(200)); + + String tmailWebImageTag = Optional.ofNullable(System.getenv(TMAIL_WEB_LATEST_IMAGE_ENV)) + .orElse("tmail-web:integration-test"); + this.tmailWeb = new GenericContainer<>(tmailWebImageTag) + .withNetworkAliases("tmail-web") + .withNetwork(network) + .dependsOn(nginx) + .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withName("tmail-web-testing" + UUID.randomUUID())) + .waitingFor(Wait.forHttp("/").forStatusCode(200)) + .withExposedPorts(TMAIL_WEB_PORT); + + this.httpClient = HttpClient.newHttpClient(); + } + + public TmailExtension() { + this(IsolationLevel.LOW); + } + + @Override + public void beforeAll(ExtensionContext context) { + if (isolationLevel == IsolationLevel.LOW) { + startContainers(); + } + } + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + if (isolationLevel == IsolationLevel.LOW) { + provisionBackendData(); + } + if (isolationLevel.equals(IsolationLevel.HIGH)) { + startContainers(); + provisionBackendData(); + } + } + + @Override + public void afterEach(ExtensionContext extensionContext) throws Exception { + if (isolationLevel == IsolationLevel.LOW) { + cleanupBackendData(); + } + if (isolationLevel.equals(IsolationLevel.HIGH)) { + stopContainers(); + } + } + + @Override + public void afterAll(ExtensionContext context) { + stopContainers(); + } + + private void startContainers() { + tmailBackend.start(); + + // we configure NGINX to tell tmail-backend how to response apiURL during runtime (as apiURL would be dynamic because of TestContainer random port nature) + nginx.start(); + configureNginxProxyForJmap(); + + setupTmailWebEnvFile(); + tmailWeb.start(); + } + + private void stopContainers() { + Runnables.runParallel( + tmailBackend::stop, + nginx::stop, + tmailWeb::stop); + } + + private void configureNginxProxyForJmap() { + try { + // Create a temporary file for the updated nginx.conf + Path tempNginxConf = Files.createTempFile("nginx", ".conf"); + + // Write the updated nginx configuration to the temporary file + Files.write(tempNginxConf, (""" + events { + worker_connections 1024; + } + + http { + upstream tmail-backend { + server tmail-backend:80; + } + + server { + listen 80; + + location / { + proxy_pass http://tmail-backend; + proxy_set_header X-JMAP-PREFIX %s; + proxy_set_header Host $host; + } + } + } + """.formatted(getProxiedJmapUrl())).getBytes()); + + // Copy the updated nginx.conf to the running container + nginx.copyFileToContainer(MountableFile.forHostPath(tempNginxConf), "/tmp/nginx.conf"); + + // Move the updated configuration to the nginx configuration directory + nginx.execInContainer("mv", "/tmp/nginx.conf", "/etc/nginx/nginx.conf"); + + // Reload nginx configuration + nginx.execInContainer("nginx", "-s", "reload"); + } catch (Exception e) { + throw new RuntimeException("Failed to create or copy temporary env.file", e); + } + } + + private void setupTmailWebEnvFile() { + try { + // Create a temporary file with the desired content + File tempFile = File.createTempFile("env", ".file"); + try (FileWriter writer = new FileWriter(tempFile)) { + writer.write("SERVER_URL=" + getProxiedJmapUrl() + "\n"); + writer.write("DOMAIN_REDIRECT_URL=http://localhost:3000\n"); + } + + // Copy the temporary file into the container + tmailWeb.withCopyFileToContainer(MountableFile.forHostPath(tempFile.getAbsolutePath()), "/usr/share/nginx/html/assets/env.file"); + } catch (IOException e) { + throw new RuntimeException("Failed to create or copy temporary env.file", e); + } + } + + public URL getTmailWebUrl() { + try { + return new URI("http://" + + tmailWeb.getHost() + ":" + + tmailWeb.getMappedPort(TMAIL_WEB_PORT)).toURL(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private URL getProxiedJmapUrl() { + try { + return new URI("http://" + + nginx.getHost() + ":" + + nginx.getMappedPort(NGINX_HTTP_PORT)).toURL(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void provisionBackendData() throws Exception { + createDomain(DOMAIN); + createUser(BOB, PASSWORD); + } + + public void createDomain(String domain) throws Exception { + tmailBackend.execInContainer("james-cli", "AddDomain", domain); + } + + public void createUser(String username, String password) throws IOException, InterruptedException { + tmailBackend.execInContainer("james-cli", "AddUser", username, password); + } + + private void cleanupBackendData() throws Exception { + deleteUser(BOB); + deleteDomain(DOMAIN); + } + + public void deleteDomain(String domain) throws Exception { + deleteUsersDataOfDomain(domain); + tmailBackend.execInContainer("james-cli", "RemoveDomain", domain); + } + + public void deleteUser(String username) throws Exception { + deleteUserData(username); + tmailBackend.execInContainer("james-cli", "RemoveUser", username); + } + + private URL getTMailBackendWebadminUrl() { + try { + return new URI("http://" + + tmailBackend.getHost() + ":" + + tmailBackend.getMappedPort(WEB_ADMIN_PORT)).toURL(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void deleteUserData(String username) throws Exception { + String deleteUserDataUrl = getTMailBackendWebadminUrl() + "/users/" + username + "?action=deleteData"; + + HttpRequest request = HttpRequest.newBuilder() + .uri(new URI(deleteUserDataUrl)) + .POST(HttpRequest.BodyPublishers.noBody()) + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 201) { + throw new RuntimeException("Failed to delete user data for username: " + username); + } + + // Extract taskId from response body + Pattern pattern = Pattern.compile("\"taskId\":\"([^\"]+)\""); + Matcher matcher = pattern.matcher(response.body()); + if (matcher.find()) { + String taskId = matcher.group(1); + awaitTaskCompletion(taskId); + } else { + throw new RuntimeException("Failed to extract taskId from response for username: " + username); + } + } + + private void deleteUsersDataOfDomain(String domain) throws Exception { + String deleteUsersDataOfDomainUrl = getTMailBackendWebadminUrl() + "/domains/" + domain + "?action=deleteData"; + + HttpRequest request = HttpRequest.newBuilder() + .uri(new URI(deleteUsersDataOfDomainUrl)) + .POST(HttpRequest.BodyPublishers.noBody()) + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 201) { + throw new RuntimeException("Failed to delete user data for domain: " + domain); + } + + // Extract taskId from response body + Pattern pattern = Pattern.compile("\"taskId\":\"([^\"]+)\""); + Matcher matcher = pattern.matcher(response.body()); + if (matcher.find()) { + String taskId = matcher.group(1); + awaitTaskCompletion(taskId); + } else { + throw new RuntimeException("Failed to extract taskId from response for domain: " + domain); + } + } + + private void awaitTaskCompletion(String taskId) throws Exception { + String taskStatusUrl = getTMailBackendWebadminUrl() + "/tasks/" + taskId + "/await"; + HttpRequest request = HttpRequest.newBuilder() + .uri(new URI(taskStatusUrl)) + .GET() + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + throw new RuntimeException("Failed to get task status for taskId: " + taskId); + } + } +} diff --git a/tmail_integration_test/src/test/java/com/tmail/preprod/oidc/composeEmail/ComposeEmailTest.java b/tmail_integration_test/src/test/java/com/tmail/preprod/oidc/composeEmail/ComposeEmailTest.java new file mode 100644 index 0000000000..607a4e4776 --- /dev/null +++ b/tmail_integration_test/src/test/java/com/tmail/preprod/oidc/composeEmail/ComposeEmailTest.java @@ -0,0 +1,16 @@ +// package com.tmail.preprod.oidc.composeEmail; + +// import com.tmail.base.TestBase; +// import com.tmail.scenarios.ComposeEmailScenario; + +// public class ComposeEmailTest extends TestBase { + +// ComposeEmailTest() { +// scenario = new ComposeEmailScenario( +// properties.getProperty("app.hostUrl"), +// properties.getProperty("user.name"), +// properties.getProperty("user.password"), +// properties.getProperty("user.additionalMailRecipent")); +// } + +// } diff --git a/tmail_integration_test/src/test/java/com/tmail/preprod/oidc/login/OidcLoginTest.java b/tmail_integration_test/src/test/java/com/tmail/preprod/oidc/login/OidcLoginTest.java new file mode 100644 index 0000000000..ff429c284c --- /dev/null +++ b/tmail_integration_test/src/test/java/com/tmail/preprod/oidc/login/OidcLoginTest.java @@ -0,0 +1,15 @@ +// package com.tmail.preprod.oidc.login; + +// import com.tmail.base.TestBase; +// import com.tmail.scenarios.OidcLoginScenario; + +// public class OidcLoginTest extends TestBase { + +// OidcLoginTest() { +// scenario = new OidcLoginScenario( +// properties.getProperty("app.hostUrl"), +// properties.getProperty("user.name"), +// properties.getProperty("user.password")); +// } + +// } diff --git a/tmail_integration_test/src/test/resources/junit-platform.properties b/tmail_integration_test/src/test/resources/junit-platform.properties new file mode 100644 index 0000000000..4570caf069 --- /dev/null +++ b/tmail_integration_test/src/test/resources/junit-platform.properties @@ -0,0 +1,5 @@ +junit.jupiter.execution.parallel.enabled = true +junit.jupiter.execution.parallel.mode.default = same_thread +junit.jupiter.execution.parallel.mode.classes.default = concurrent +junit.jupiter.execution.parallel.config.strategy=dynamic +junit.jupiter.execution.parallel.config.dynamic.factor=0.5 \ No newline at end of file diff --git a/tmail_integration_test/src/test/resources/tmail-backend-conf/imapserver.xml b/tmail_integration_test/src/test/resources/tmail-backend-conf/imapserver.xml new file mode 100644 index 0000000000..db917d5958 --- /dev/null +++ b/tmail_integration_test/src/test/resources/tmail-backend-conf/imapserver.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tmail_integration_test/src/test/resources/tmail-backend-conf/jmap.properties b/tmail_integration_test/src/test/resources/tmail-backend-conf/jmap.properties new file mode 100644 index 0000000000..c1741f80ce --- /dev/null +++ b/tmail_integration_test/src/test/resources/tmail-backend-conf/jmap.properties @@ -0,0 +1,2 @@ +# So NGINX can override the dynamic tmail-backend JMAP URL via the session route +dynamic.jmap.prefix.resolution.enabled=true \ No newline at end of file diff --git a/tmail_integration_test/src/test/resources/tmail-backend-conf/jwt_privatekey b/tmail_integration_test/src/test/resources/tmail-backend-conf/jwt_privatekey new file mode 100644 index 0000000000..a4aa851ff5 --- /dev/null +++ b/tmail_integration_test/src/test/resources/tmail-backend-conf/jwt_privatekey @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEV3BhLSBHuApH +GNlquIczzcvDOc0HoqczATsM7CeRp2SUicioxlItL2q6tKGTsK1zCcM+uKw1ydxd +AGDxpBD6kqIel/Quu/dRd8H6P/f4Hr0OOxGCgc78FYvjY6gcbPtaOmD9u/RMRqP6 +95s/DriPxPDYzA58yRH7NlhzJymthoZEtwIL/SuZ6e2huqnJdC11wEJgkwwkKO3x +01Y84V2y2EdRCvb5j9EOkCC5C4TGpjvSffD/4mcj+Z2VNTxQV0KdrlNxhBwwFEbL +TQ2B5hIkH2INDP6ubkxJeXmZ3iXyw+iNKtBUef9ZavIk4lZLKYNjZWX2eBg2cokT +p759oRoHAgMBAAECggEAdmyrCuH2C2wVPubdFIKygeuKEHnHkehoYtpGLLgv8al+ +gB1PG4VrQXfNL0oN/w/cvntP+X/X1yWnNa0py/YCi7Bv+nX6wUl8lfXe2TtGLLEV +pQS5vfbfyqqQUpnkZyjQvo5hvAlnA67D73bze6g8Z/MItir2PgvlPZl85g/kEpX3 +zt1yDTUFe+L8Ur+8Lied/0w2M53lUlebIYtsa2W7I05YzUBAVXkCIffnaIt3QtTC +tppBcVZXUacRtqULBMcUE2zc/yUKNqHzBjlkBv4VQ7nQDOjjUfW+VtccnTr+mLnl +R7VDy+POuZQ5u0gA8IwJNJFd5qdIm7l4tG7xa69rMQKBgQDmX1FF5Zty0Q7Acp1n +G9TZBOTTehzQPYsJwMynLR/b8mUAL+FTCh7Q7OJkGhcBxVDgc34OVPvc9wlFSUuU +ac0C0GtcD3BidatEfwMqVdpwcDnSEK47N13oSmaAL21mgC6/0ypV5TBgbkMRcxSb +h1GdeBWEG1RluVx6n1TflSvw6QKBgQDaLvj3fNbcIfJubx5oP2kmA1rw5jcSShK4 +UgM4Ifj98LOmsiB6qfY+36p6D78XINV0KpS3THi8rWzf31OuTN1BoZ5UpcyOOrDb +2pNnfGpC1VBP4rfWJMNpcGstj3YUNEV5pLyd5Cd6/gV8nRgiQ9ccEDJ1I5GXVWfV ++2a7/zddbwKBgHqWWi8xoWiVqp3p36yQeNEK86E9J7wAI86K09xZ/MwTzn8s+2Au +0HsossfFwlxk3Uay7m899dB9fGdsO1W8fyVyNs8EQC+EoiCO3eZXTSfr8DjCO5Sz +P7tua+DmW/bhWv8kpTCUBwwpYHMWo+6nMVz0G67yxBRlcLqnsohPXtSRAoGBAMMD +MxJ6Kc1OJlMgzKve6YvJefJRwq19Oag33ZrBerz29IwtMCyTV36xCb3Z7zGr7j3L +hWskVdJGrEaZZUEogKaV31/HZcNGoCeSASiBIrUj1ongmfI0n9jRW2q4jJDYe7ST +UudJMySSgbL08spFmrIBpCfhJ9N8ybeP4i5smj7PAoGBANSfn+DzQPICp2rldw7y +rPSCywIM6LzdoyykRMmX04sQAFVKgJwPei+oLrg8HcUjCZ1t8KJ4tHsvqFkQ0O4s +q8eh2eli5Qppg/Qmx1zT1W7+JYxlPsiXfmViBcY2+yNjNOHQPzyJyE+pvybW0DC+ +k9EJZv81OGrKInhjB/Ep6C76 +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/tmail_integration_test/src/test/resources/tmail-backend-conf/jwt_publickey b/tmail_integration_test/src/test/resources/tmail-backend-conf/jwt_publickey new file mode 100644 index 0000000000..ea8beb9de0 --- /dev/null +++ b/tmail_integration_test/src/test/resources/tmail-backend-conf/jwt_publickey @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxFdwYS0gR7gKRxjZariH +M83LwznNB6KnMwE7DOwnkadklInIqMZSLS9qurShk7CtcwnDPrisNcncXQBg8aQQ ++pKiHpf0Lrv3UXfB+j/3+B69DjsRgoHO/BWL42OoHGz7Wjpg/bv0TEaj+vebPw64 +j8Tw2MwOfMkR+zZYcycprYaGRLcCC/0rmentobqpyXQtdcBCYJMMJCjt8dNWPOFd +sthHUQr2+Y/RDpAguQuExqY70n3w/+JnI/mdlTU8UFdCna5TcYQcMBRGy00NgeYS +JB9iDQz+rm5MSXl5md4l8sPojSrQVHn/WWryJOJWSymDY2Vl9ngYNnKJE6e+faEa +BwIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/tmail_integration_test/src/test/resources/tmail-backend-conf/smtpserver.xml b/tmail_integration_test/src/test/resources/tmail-backend-conf/smtpserver.xml new file mode 100644 index 0000000000..84834caa19 --- /dev/null +++ b/tmail_integration_test/src/test/resources/tmail-backend-conf/smtpserver.xml @@ -0,0 +1,2 @@ + +