Compare commits
473 Commits
payments
...
feature/co
| Author | SHA1 | Date | |
|---|---|---|---|
| 298f73b02b | |||
| bb0d3c253a | |||
| ccef0fd186 | |||
| 6ff2d77a32 | |||
| ecfaaec161 | |||
| 4f6e3684bf | |||
| abdbc2bf2e | |||
| 54b81818ec | |||
| 62b38acab4 | |||
| 924ecd83ee | |||
| 4510d2bb28 | |||
| f54bf24417 | |||
| b5d12f31a1 | |||
| 74f470219a | |||
| 24ddbf34e4 | |||
| 69297f826e | |||
| 390f2501b4 | |||
| 555a668481 | |||
| 5aa0c0acc7 | |||
| 57f0f64d08 | |||
| 9f23ecb42e | |||
| 8ff94a1e92 | |||
| a87c7e8732 | |||
| 4be46f71c7 | |||
| 105211e334 | |||
| 6d2d16d8cb | |||
| 424f2af0cc | |||
| 41f1797dcf | |||
| cf6a9dd6df | |||
| 0d677afd32 | |||
| 323d28bb17 | |||
| 92f6035b84 | |||
| c5c85c4fda | |||
| 4cfee8ced3 | |||
| 376e6bc13a | |||
| 4422f93903 | |||
| a9a34fbbc5 | |||
| 04b8d1609c | |||
| e901b22981 | |||
| 2d87cd5aee | |||
| bef87262b1 | |||
| 3f28aa95bc | |||
| dd8faa9f35 | |||
| bc946cfc39 | |||
| c4bdf707e1 | |||
| 17d931ffda | |||
| a21a8af9b1 | |||
| 05c96bc10e | |||
| bbdaa25e12 | |||
| 440adcdf8d | |||
| 6193c97802 | |||
| a985ced85c | |||
| 51901cc639 | |||
| c7fefe2a8b | |||
| cb897a3243 | |||
| 3325df94f7 | |||
| 0aefed0163 | |||
| 516197b819 | |||
| d9465996d7 | |||
| 309c7b1d87 | |||
| ab1a0a88f9 | |||
| 5be9136e06 | |||
| ac493725cf | |||
| 99da6e12fe | |||
| 383ea3d053 | |||
| 929ccadb0f | |||
| 144e48f370 | |||
| 53edf0b7e1 | |||
| 773312d5f9 | |||
| 003604444c | |||
| c7ed2f16c1 | |||
| 92c922a130 | |||
| f0666d9848 | |||
| 6e6225d6b6 | |||
| c23ae39b87 | |||
| cff71245ae | |||
| 3065b78779 | |||
| c1e498b1ab | |||
| 81c3eaec70 | |||
| 0f2d9ba601 | |||
| 82571e6035 | |||
| ba76348936 | |||
| a181ae4724 | |||
| 4d2cd62267 | |||
| fb281caf99 | |||
| a2ef28a1b5 | |||
| cbaee6d597 | |||
| e37adc1f78 | |||
| 3485e430f7 | |||
| 731787b002 | |||
| c9629c32e3 | |||
| 5c6eb97c1f | |||
| 1961be3805 | |||
| 6ff11b8c1e | |||
| 460fbffcad | |||
| b93fac4614 | |||
| 9622cc2d64 | |||
| 79e8c0fe74 | |||
| 8d5a2c8e56 | |||
| ad0b8d209a | |||
| 653ea9ab56 | |||
| 8c269e8c47 | |||
| a197d5bc28 | |||
| 3f9c298b6f | |||
| 3b1534d3b3 | |||
| eb2bde8d40 | |||
| c3dcc6febc | |||
| c4d328d92c | |||
| 2eee3489cd | |||
| 3b57d0e70d | |||
| fe9476d417 | |||
| 79d0f72f08 | |||
| 5925a97b01 | |||
| 41b22ad457 | |||
| 66a08c8016 | |||
| 3449ff9afd | |||
| d4fbbb8d4b | |||
| 3147566241 | |||
| f7e69b1184 | |||
| 41324c61bd | |||
| b8bf71fbe3 | |||
| 6d49e604be | |||
| 21dcafec26 | |||
| 8e8243345a | |||
| fe38e477e3 | |||
| db47543252 | |||
| caf77b1fd9 | |||
| dcc786d376 | |||
| 8cd01c6f3b | |||
| 1c98c0842d | |||
| 59cced7b17 | |||
| 4e14534b1b | |||
| 72d0c79c74 | |||
| 5b1826a10d | |||
| 039f2bb051 | |||
| 9d6953dbf5 | |||
| 90048ac159 | |||
| 982dee6c7a | |||
| 09897b7f69 | |||
| 09a625530e | |||
| c60761adab | |||
| 6694a4b0ce | |||
| 850796e1ca | |||
| 051424f58b | |||
| ac986ac360 | |||
| cf86570e4c | |||
| e30f5dabcc | |||
| 79039b99e2 | |||
| 14720b66bf | |||
| d7308229a0 | |||
| f7d3dbfd27 | |||
| bd7c47351f | |||
| 6cf994cd5d | |||
| fa3d7aa1fd | |||
| 5b4d31e2f1 | |||
| 2e6769f18f | |||
| 7d20b56583 | |||
| 40d55b0b43 | |||
| 5aa45b3d01 | |||
| ecfb4cc7d2 | |||
| 6b1e571341 | |||
| f6ff53bcd7 | |||
| d84edc11a9 | |||
| 45b2842e5a | |||
| a4f57f780b | |||
| e130f4a037 | |||
| 5c30dd9224 | |||
| 836aea707a | |||
| e0e7815ad8 | |||
| e0dde50eba | |||
| 54ddf68c22 | |||
| b5bba037f1 | |||
| 6ed36dba75 | |||
| 4deb263c7e | |||
| 82786b3577 | |||
| ff48b873e9 | |||
| 35948998f6 | |||
| c034d781af | |||
| 5bebe110fc | |||
| 107a4ec593 | |||
| 03effaed13 | |||
| 32eb4e0d52 | |||
| 6f5855e2fd | |||
| 412cb96888 | |||
| 853b6f20a3 | |||
| 01cb4c9427 | |||
| 82123a6d5f | |||
| 3956a87862 | |||
| 7251349e1d | |||
| cf2dbbeb63 | |||
| e9cceae485 | |||
| 6de01b62ae | |||
| 7c7ffb8f3d | |||
| 80f95bae5a | |||
| 63547b0f37 | |||
| 9ab78ac965 | |||
| 375e613caf | |||
| 9b253dd545 | |||
| 3f3fb3d5d0 | |||
| e48dec979c | |||
| 1c30318e06 | |||
| d5d38637a7 | |||
| ac5219f389 | |||
| 4fbdce3c8c | |||
| 5ad0a7acc5 | |||
| 065433ff61 | |||
| c06fb06d03 | |||
| 04c26e83cf | |||
| dc7325ea65 | |||
| 76782fbfd4 | |||
| c17e94ff7f | |||
| c84287e803 | |||
| 44c8949c07 | |||
| aaecc38461 | |||
| 3470e1bfef | |||
| 0530f892f2 | |||
| 734bd79af7 | |||
| 94e2fcbf7d | |||
| 35a943c066 | |||
| 5193e6ada2 | |||
| 2052fdcf85 | |||
| 4e50384dd9 | |||
| 9f5ec3f1da | |||
| db3197a93a | |||
| b90eed2a54 | |||
| 118be4c6c0 | |||
| 62de343dae | |||
| df92c51344 | |||
| 221d053d5f | |||
| e5cf5fcb61 | |||
| 3e427f44d7 | |||
| 746230a541 | |||
| 86642b9587 | |||
| 71ffc52993 | |||
| d355ee2442 | |||
| cc5159fc56 | |||
| d6d82d20c6 | |||
| f2d2385f24 | |||
| e6974c7be7 | |||
| 20cebc8bc7 | |||
| 2247833203 | |||
| 92e93a2b69 | |||
| 691dfc0472 | |||
| 2b9b6aa215 | |||
| 4cd4be24e6 | |||
| a547f7a786 | |||
| 42698631a3 | |||
| 69fdc2233f | |||
| 75b47e2c25 | |||
| 1c0a8b7bb7 | |||
| 417b6660fc | |||
| b8ac786146 | |||
| dd1617939b | |||
| 4c85af38aa | |||
| 309ff8b8b7 | |||
| e040944965 | |||
| b6526f20ee | |||
| 0418f16f87 | |||
| f36ad5e4a6 | |||
| 0a50941c2b | |||
| 7746d08759 | |||
| 72c88cc4b0 | |||
| b21b234b9a | |||
| f89bca99b3 | |||
| 1056895c31 | |||
| 424b8d9034 | |||
| 4aa91c355e | |||
| 7ea415cb6e | |||
| 21fd1e0197 | |||
| d618ed76d0 | |||
| dfd7ba9c41 | |||
| b8f5c5d6f8 | |||
| d470ed470a | |||
| a400fef77d | |||
| febc21a590 | |||
| 29fca859fc | |||
| d92fe887fd | |||
| 315e5b2908 | |||
| 244e5bbd03 | |||
| a86041885c | |||
| 12011ce525 | |||
| c92e2fb67f | |||
| 7e1ead9cae | |||
| e59ce36033 | |||
| aa3ffdb6a7 | |||
| 2eddb99c47 | |||
| c461519597 | |||
| 919ee55c45 | |||
| f5350f5e78 | |||
| b9b49f0b26 | |||
| afa916a30d | |||
| 4347cefaed | |||
| e7ebe7f403 | |||
| ed41b82076 | |||
| 9470f54867 | |||
| f82d222df3 | |||
| fd8ef27185 | |||
| 5b6ed5cf16 | |||
| bf1032245a | |||
| fad2c8792c | |||
| 73d9de45a2 | |||
| 56d89fcdc4 | |||
| eff6f01924 | |||
| 72d44b81df | |||
| 2942d7393e | |||
| 0b160758e2 | |||
| ecbb6d1e76 | |||
| e7a4653c01 | |||
| 05ffe572c8 | |||
| cbc40f7d95 | |||
| 27e26ca921 | |||
| e83adbfdbf | |||
| 973fc2490c | |||
| e148b9fdfa | |||
| 238c15888b | |||
| ddc5086b3b | |||
| 769e8fea27 | |||
| 297fa8241a | |||
| 984a87f200 | |||
| cda889a15b | |||
| 1230a27d94 | |||
| bc46f31434 | |||
| 514daf9c7c | |||
| 51a3979c03 | |||
| c7e32d1399 | |||
| 4e21e8d698 | |||
| 703b1e9fba | |||
| 2fe5a2399d | |||
| 9e41090712 | |||
| 648d0fc04b | |||
| 56e437ff13 | |||
| 88c1111bd5 | |||
| 85be483c4e | |||
| 08e099fc37 | |||
| 8a97304ff5 | |||
| 8c1ca94a08 | |||
| cbaff2e763 | |||
| f36bc9afc1 | |||
| 95a03434ca | |||
| 6b2034612a | |||
| ec14ad49e5 | |||
| 03998f9cf1 | |||
| 811e92defc | |||
| 1e60b38087 | |||
| 693f55369c | |||
| 506dd5a80f | |||
| 7445021cf3 | |||
| 63a4113d81 | |||
| d352aec5be | |||
| 60558a4fcf | |||
| 29887818f9 | |||
| 42ec003b05 | |||
| 4728e25803 | |||
| 7b91447cad | |||
| d84c856ce7 | |||
| 81284d7efe | |||
| c1954497b8 | |||
| 9cdb4c7724 | |||
| a560e19db2 | |||
| c263e4227e | |||
| 3a375044b2 | |||
| 4a57bb35c5 | |||
| 3c4159ae8c | |||
| 3d267aff37 | |||
| b63b06ef14 | |||
| 8ade5ad3d7 | |||
| 26e89fb177 | |||
| 1810dc6e2a | |||
| 4d2d25f47b | |||
| c79cbeffcc | |||
| b7614a39f1 | |||
| a05c167f30 | |||
| c1c903ac93 | |||
| a0a782c91b | |||
| c140daa7ae | |||
| 6d30a59651 | |||
| 8d453dc980 | |||
| 0a50de3d70 | |||
| 02053182db | |||
| 33f3dfa252 | |||
| b6deb4b371 | |||
| cb70973d3b | |||
| 47c7821b0c | |||
| a07e9c23ca | |||
| 1ffeea8b77 | |||
| 5f484036f8 | |||
| 73927557ca | |||
| 5111d5d65f | |||
| ced0895063 | |||
| 34e7a7c60f | |||
| c89f1c666e | |||
| 33c2403aef | |||
| 0088d146f0 | |||
| 94c042d403 | |||
| 48cb23379c | |||
| e526dce2c9 | |||
| cacc2460f1 | |||
| dd53db6795 | |||
| 435a9c04f9 | |||
| 8e3a27e0d3 | |||
| 48d2430c9c | |||
| cf0c55eafe | |||
| 03c6633504 | |||
| b8184f02ec | |||
| c12d1924c4 | |||
| 869f33f1f1 | |||
| a07246130e | |||
| 67aafafd1e | |||
| c929e1e2d7 | |||
| 990266ba95 | |||
| fa36037aac | |||
| c9e2adf692 | |||
| 995b69eb65 | |||
| 88269c40f8 | |||
| f1226b4c18 | |||
| b636550619 | |||
| 797d236547 | |||
| 90447ce9a0 | |||
| 22ef648b41 | |||
| ae4bc7824a | |||
| 4eb4ac81ce | |||
| 01d9d4241b | |||
| 907eaf8d33 | |||
| 440bbcac66 | |||
| 904cfee2a9 | |||
| 69b3cf358a | |||
| ec4e42b408 | |||
| 9bf06f2480 | |||
| 903e1991e1 | |||
| f9d8f59195 | |||
| ffc0a1f103 | |||
| 53cadd8499 | |||
| b4bb90d357 | |||
| 5ee7852ee7 | |||
| 2b3169b3e5 | |||
| 3cbf936583 | |||
| bb666e8ab6 | |||
| 76c7eb606f | |||
| e4000d57fc | |||
| fd8574e631 | |||
| 644d1c2abe | |||
| 01de94876b | |||
| 74309006b1 | |||
| e8d8e4a022 | |||
| cf03bbc1b5 | |||
| 014ef1ad26 | |||
| 5a68dfb3df | |||
| 8445af3f5a | |||
| 471aa1067c | |||
| d8afa49897 | |||
| 5f1e125cff | |||
| f76a88a53c | |||
| e581895246 | |||
| 5186c21e9a | |||
| 4f5e69c6fc | |||
| e4c0a23ded | |||
| a53cfb24ea | |||
| f8ff70c35d | |||
| 46b7ba4a1d | |||
| da90ed94ea | |||
| 3283494cb1 | |||
| 6049ce0bee | |||
| 1cd18b002c | |||
| 16a5ed2e80 | |||
| 54818ead25 | |||
| 3955fbf4bc | |||
| d515d0596f | |||
| 880dd85d2b | |||
| cd578352f9 | |||
| 12edcd0940 | |||
| 60a49060a1 | |||
| 337a00e98f | |||
| 59d566e9a6 |
@@ -1,31 +0,0 @@
|
||||
Extension Discovery Cache
|
||||
=========================
|
||||
|
||||
This folder is used by `package:extension_discovery` to cache lists of
|
||||
packages that contains extensions for other packages.
|
||||
|
||||
DO NOT USE THIS FOLDER
|
||||
----------------------
|
||||
|
||||
* Do not read (or rely) the contents of this folder.
|
||||
* Do write to this folder.
|
||||
|
||||
If you're interested in the lists of extensions stored in this folder use the
|
||||
API offered by package `extension_discovery` to get this information.
|
||||
|
||||
If this package doesn't work for your use-case, then don't try to read the
|
||||
contents of this folder. It may change, and will not remain stable.
|
||||
|
||||
Use package `extension_discovery`
|
||||
---------------------------------
|
||||
|
||||
If you want to access information from this folder.
|
||||
|
||||
Feel free to delete this folder
|
||||
-------------------------------
|
||||
|
||||
Files in this folder act as a cache, and the cache is discarded if the files
|
||||
are older than the modification time of `.dart_tool/package_config.json`.
|
||||
|
||||
Hence, it should never be necessary to clear this cache manually, if you find a
|
||||
need to do please file a bug.
|
||||
@@ -1 +0,0 @@
|
||||
{"version":2,"entries":[{"package":"sf_app_platform_mono_repo","rootUri":"../","packageUri":"lib/"}]}
|
||||
@@ -1,364 +0,0 @@
|
||||
{
|
||||
"configVersion": 2,
|
||||
"packages": [
|
||||
{
|
||||
"name": "ansi_styles",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/ansi_styles-0.3.2+1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/args-2.7.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "async",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/async-2.13.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "characters",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/characters-1.4.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "charcode",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/charcode-1.4.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "checked_yaml",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/checked_yaml-2.0.4",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.8"
|
||||
},
|
||||
{
|
||||
"name": "cli_launcher",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/cli_launcher-0.3.2+1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.8"
|
||||
},
|
||||
{
|
||||
"name": "cli_util",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/cli_util-0.4.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "collection",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/collection-1.19.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "conventional_commit",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/conventional_commit-0.6.1+1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.8"
|
||||
},
|
||||
{
|
||||
"name": "ffi",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/ffi-2.1.4",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.7"
|
||||
},
|
||||
{
|
||||
"name": "file",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/file-7.0.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "flutter",
|
||||
"rootUri": "file:///Users/juliandalcalaf/Development/flutter/packages/flutter",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.8"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_linux",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_macos",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_platform_interface",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_platform_interface-1.1.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_web",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_windows",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "flutter_web_plugins",
|
||||
"rootUri": "file:///Users/juliandalcalaf/Development/flutter/packages/flutter_web_plugins",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.8"
|
||||
},
|
||||
{
|
||||
"name": "glob",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/glob-2.1.3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "graphs",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/graphs-2.3.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "http",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/http-1.5.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "http_parser",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/http_parser-4.1.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "io",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/io-1.0.5",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "js",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/js-0.6.7",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.19"
|
||||
},
|
||||
{
|
||||
"name": "json_annotation",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/json_annotation-4.9.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "material_color_utilities",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.17"
|
||||
},
|
||||
{
|
||||
"name": "melos",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/melos-6.3.3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.8"
|
||||
},
|
||||
{
|
||||
"name": "meta",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/meta-1.16.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "mustache_template",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/mustache_template-2.0.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.7"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path-1.9.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "path_provider",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider-2.1.5",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "path_provider_android",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_android-2.2.20",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.9"
|
||||
},
|
||||
{
|
||||
"name": "path_provider_foundation",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.9"
|
||||
},
|
||||
{
|
||||
"name": "path_provider_linux",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.19"
|
||||
},
|
||||
{
|
||||
"name": "path_provider_platform_interface",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_platform_interface-2.1.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "path_provider_windows",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.2"
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/platform-3.1.6",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.2"
|
||||
},
|
||||
{
|
||||
"name": "plugin_platform_interface",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
},
|
||||
{
|
||||
"name": "pool",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/pool-1.5.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "process",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/process-5.0.5",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.5"
|
||||
},
|
||||
{
|
||||
"name": "prompts",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/prompts-2.0.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "pub_semver",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/pub_semver-2.2.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "pub_updater",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/pub_updater-0.5.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.5"
|
||||
},
|
||||
{
|
||||
"name": "pubspec_parse",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/pubspec_parse-1.5.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.6"
|
||||
},
|
||||
{
|
||||
"name": "sky_engine",
|
||||
"rootUri": "file:///Users/juliandalcalaf/Development/flutter/bin/cache/pkg/sky_engine",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.8"
|
||||
},
|
||||
{
|
||||
"name": "source_span",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/source_span-1.10.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "stack_trace",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/stack_trace-1.12.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "string_scanner",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/string_scanner-1.4.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "term_glyph",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/term_glyph-1.2.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "typed_data",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/typed_data-1.4.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.5"
|
||||
},
|
||||
{
|
||||
"name": "vector_math",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/vector_math-2.2.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "web",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/web-1.1.1",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "win32",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/win32-5.15.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.8"
|
||||
},
|
||||
{
|
||||
"name": "xdg_directories",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/xdg_directories-1.1.0",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.3"
|
||||
},
|
||||
{
|
||||
"name": "yaml",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/yaml-3.1.3",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.4"
|
||||
},
|
||||
{
|
||||
"name": "yaml_edit",
|
||||
"rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/yaml_edit-2.2.2",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.1"
|
||||
},
|
||||
{
|
||||
"name": "sf_app_platform_mono_repo",
|
||||
"rootUri": "../",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "3.0"
|
||||
}
|
||||
],
|
||||
"generator": "pub",
|
||||
"generatorVersion": "3.9.2",
|
||||
"flutterRoot": "file:///Users/juliandalcalaf/Development/flutter",
|
||||
"flutterVersion": "3.35.7",
|
||||
"pubCache": "file:///Users/juliandalcalaf/.pub-cache"
|
||||
}
|
||||
@@ -1,491 +0,0 @@
|
||||
{
|
||||
"roots": [
|
||||
"sf_app_platform_mono_repo"
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"name": "sf_app_platform_mono_repo",
|
||||
"version": "0.0.0",
|
||||
"dependencies": [
|
||||
"flutter_secure_storage"
|
||||
],
|
||||
"devDependencies": [
|
||||
"melos"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage",
|
||||
"version": "9.2.4",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"flutter_secure_storage_linux",
|
||||
"flutter_secure_storage_macos",
|
||||
"flutter_secure_storage_platform_interface",
|
||||
"flutter_secure_storage_web",
|
||||
"flutter_secure_storage_windows",
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "melos",
|
||||
"version": "6.3.3",
|
||||
"dependencies": [
|
||||
"ansi_styles",
|
||||
"args",
|
||||
"async",
|
||||
"cli_launcher",
|
||||
"cli_util",
|
||||
"collection",
|
||||
"conventional_commit",
|
||||
"file",
|
||||
"glob",
|
||||
"graphs",
|
||||
"http",
|
||||
"meta",
|
||||
"mustache_template",
|
||||
"path",
|
||||
"platform",
|
||||
"pool",
|
||||
"prompts",
|
||||
"pub_semver",
|
||||
"pub_updater",
|
||||
"pubspec_parse",
|
||||
"string_scanner",
|
||||
"yaml",
|
||||
"yaml_edit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "meta",
|
||||
"version": "1.16.0",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_windows",
|
||||
"version": "3.1.2",
|
||||
"dependencies": [
|
||||
"ffi",
|
||||
"flutter",
|
||||
"flutter_secure_storage_platform_interface",
|
||||
"path",
|
||||
"path_provider",
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_web",
|
||||
"version": "1.2.1",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"flutter_secure_storage_platform_interface",
|
||||
"flutter_web_plugins",
|
||||
"js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_platform_interface",
|
||||
"version": "1.1.2",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"plugin_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_macos",
|
||||
"version": "3.1.3",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"flutter_secure_storage_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_linux",
|
||||
"version": "1.2.3",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"flutter_secure_storage_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter",
|
||||
"version": "0.0.0",
|
||||
"dependencies": [
|
||||
"characters",
|
||||
"collection",
|
||||
"material_color_utilities",
|
||||
"meta",
|
||||
"sky_engine",
|
||||
"vector_math"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "yaml_edit",
|
||||
"version": "2.2.2",
|
||||
"dependencies": [
|
||||
"collection",
|
||||
"meta",
|
||||
"source_span",
|
||||
"yaml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "yaml",
|
||||
"version": "3.1.3",
|
||||
"dependencies": [
|
||||
"collection",
|
||||
"source_span",
|
||||
"string_scanner"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "string_scanner",
|
||||
"version": "1.4.1",
|
||||
"dependencies": [
|
||||
"source_span"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pubspec_parse",
|
||||
"version": "1.5.0",
|
||||
"dependencies": [
|
||||
"checked_yaml",
|
||||
"collection",
|
||||
"json_annotation",
|
||||
"pub_semver",
|
||||
"yaml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pub_updater",
|
||||
"version": "0.5.0",
|
||||
"dependencies": [
|
||||
"http",
|
||||
"json_annotation",
|
||||
"process",
|
||||
"pub_semver"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pub_semver",
|
||||
"version": "2.2.0",
|
||||
"dependencies": [
|
||||
"collection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "prompts",
|
||||
"version": "2.0.0",
|
||||
"dependencies": [
|
||||
"charcode",
|
||||
"io"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pool",
|
||||
"version": "1.5.2",
|
||||
"dependencies": [
|
||||
"async",
|
||||
"stack_trace"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"version": "3.1.6",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"version": "1.9.1",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "mustache_template",
|
||||
"version": "2.0.2",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "http",
|
||||
"version": "1.5.0",
|
||||
"dependencies": [
|
||||
"async",
|
||||
"http_parser",
|
||||
"meta",
|
||||
"web"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "graphs",
|
||||
"version": "2.3.2",
|
||||
"dependencies": [
|
||||
"collection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "glob",
|
||||
"version": "2.1.3",
|
||||
"dependencies": [
|
||||
"async",
|
||||
"collection",
|
||||
"file",
|
||||
"path",
|
||||
"string_scanner"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "file",
|
||||
"version": "7.0.1",
|
||||
"dependencies": [
|
||||
"meta",
|
||||
"path"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "conventional_commit",
|
||||
"version": "0.6.1+1",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "collection",
|
||||
"version": "1.19.1",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "cli_util",
|
||||
"version": "0.4.2",
|
||||
"dependencies": [
|
||||
"meta",
|
||||
"path"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cli_launcher",
|
||||
"version": "0.3.2+1",
|
||||
"dependencies": [
|
||||
"path",
|
||||
"yaml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "async",
|
||||
"version": "2.13.0",
|
||||
"dependencies": [
|
||||
"collection",
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"version": "2.7.0",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "ansi_styles",
|
||||
"version": "0.3.2+1",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "win32",
|
||||
"version": "5.15.0",
|
||||
"dependencies": [
|
||||
"ffi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "path_provider",
|
||||
"version": "2.1.5",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"path_provider_android",
|
||||
"path_provider_foundation",
|
||||
"path_provider_linux",
|
||||
"path_provider_platform_interface",
|
||||
"path_provider_windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ffi",
|
||||
"version": "2.1.4",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "js",
|
||||
"version": "0.6.7",
|
||||
"dependencies": [
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter_web_plugins",
|
||||
"version": "0.0.0",
|
||||
"dependencies": [
|
||||
"flutter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "plugin_platform_interface",
|
||||
"version": "2.1.8",
|
||||
"dependencies": [
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sky_engine",
|
||||
"version": "0.0.0",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "vector_math",
|
||||
"version": "2.2.0",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "material_color_utilities",
|
||||
"version": "0.11.1",
|
||||
"dependencies": [
|
||||
"collection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "characters",
|
||||
"version": "1.4.0",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "source_span",
|
||||
"version": "1.10.1",
|
||||
"dependencies": [
|
||||
"collection",
|
||||
"path",
|
||||
"term_glyph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "json_annotation",
|
||||
"version": "4.9.0",
|
||||
"dependencies": [
|
||||
"meta"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "checked_yaml",
|
||||
"version": "2.0.4",
|
||||
"dependencies": [
|
||||
"json_annotation",
|
||||
"source_span",
|
||||
"yaml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "process",
|
||||
"version": "5.0.5",
|
||||
"dependencies": [
|
||||
"file",
|
||||
"path",
|
||||
"platform"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "io",
|
||||
"version": "1.0.5",
|
||||
"dependencies": [
|
||||
"meta",
|
||||
"path",
|
||||
"string_scanner"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "charcode",
|
||||
"version": "1.4.0",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "stack_trace",
|
||||
"version": "1.12.1",
|
||||
"dependencies": [
|
||||
"path"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "web",
|
||||
"version": "1.1.1",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "http_parser",
|
||||
"version": "4.1.2",
|
||||
"dependencies": [
|
||||
"collection",
|
||||
"source_span",
|
||||
"string_scanner",
|
||||
"typed_data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "path_provider_windows",
|
||||
"version": "2.3.0",
|
||||
"dependencies": [
|
||||
"ffi",
|
||||
"flutter",
|
||||
"path",
|
||||
"path_provider_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "path_provider_platform_interface",
|
||||
"version": "2.1.2",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"platform",
|
||||
"plugin_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "path_provider_linux",
|
||||
"version": "2.2.1",
|
||||
"dependencies": [
|
||||
"ffi",
|
||||
"flutter",
|
||||
"path",
|
||||
"path_provider_platform_interface",
|
||||
"xdg_directories"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "path_provider_foundation",
|
||||
"version": "2.4.3",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"path_provider_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "path_provider_android",
|
||||
"version": "2.2.20",
|
||||
"dependencies": [
|
||||
"flutter",
|
||||
"path_provider_platform_interface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "term_glyph",
|
||||
"version": "1.2.2",
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"name": "typed_data",
|
||||
"version": "1.4.0",
|
||||
"dependencies": [
|
||||
"collection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "xdg_directories",
|
||||
"version": "1.1.0",
|
||||
"dependencies": [
|
||||
"meta",
|
||||
"path"
|
||||
]
|
||||
}
|
||||
],
|
||||
"configVersion": 1
|
||||
}
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
3.35.7
|
||||
@@ -1 +1 @@
|
||||
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_secure_storage","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_android-2.2.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_secure_storage_macos","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_secure_storage_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"flutter_secure_storage_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[{"name":"flutter_secure_storage_web","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2026-02-27 12:35:56.235180","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}}
|
||||
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_secure_storage","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_android-2.2.20/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_secure_storage_macos","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.3/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_secure_storage_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"flutter_secure_storage_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[{"name":"flutter_secure_storage_web","path":"/Users/juliandalcalaf/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2026-03-13 09:52:54.635963","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}}
|
||||
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# Dart / Flutter workspace caches (regenerated by `flutter pub get` / `dart pub get`)
|
||||
.dart_tool/
|
||||
**/.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
|
||||
# Per-package build outputs
|
||||
**/build/
|
||||
**/coverage/
|
||||
|
||||
# Flutter ephemeral plugin symlinks and helpers (regenerated on pub get)
|
||||
**/ios/Flutter/ephemeral/
|
||||
**/linux/flutter/ephemeral/
|
||||
**/macos/Flutter/ephemeral/
|
||||
**/windows/flutter/ephemeral/
|
||||
|
||||
# Flutter iOS build config (regenerated on pub get; contains machine-specific paths)
|
||||
**/ios/Flutter/Generated.xcconfig
|
||||
**/ios/Flutter/flutter_export_environment.sh
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
*.iml
|
||||
.vscode/
|
||||
|
||||
# App config (contains API keys, passed via --dart-define-from-file)
|
||||
apps/mobile_app/config/*.json
|
||||
|
||||
# Golden test diff outputs (transient artifacts written when goldens fail)
|
||||
**/test/failures/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
2
.idea/.name
generated
2
.idea/.name
generated
@@ -1 +1 @@
|
||||
sf-app-platform
|
||||
sf_app_platform_mono_repo
|
||||
27
.idea/modules.xml
generated
27
.idea/modules.xml
generated
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/activity/melos_activity.iml" filepath="$PROJECT_DIR$/modules/activity/melos_activity.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/auth/melos_auth.iml" filepath="$PROJECT_DIR$/modules/auth/melos_auth.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/dashboard_shell/melos_dashboard_shell.iml" filepath="$PROJECT_DIR$/modules/dashboard_shell/melos_dashboard_shell.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/design_system/melos_design_system.iml" filepath="$PROJECT_DIR$/packages/design_system/melos_design_system.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/flutter_treezor_entrust_sdk_bridge/melos_flutter_treezor_entrust_sdk_bridge.iml" filepath="$PROJECT_DIR$/packages/flutter_treezor_entrust_sdk_bridge/melos_flutter_treezor_entrust_sdk_bridge.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/flutter_treezor_entrust_sdk_bridge/example/melos_flutter_treezor_entrust_sdk_bridge_example.iml" filepath="$PROJECT_DIR$/packages/flutter_treezor_entrust_sdk_bridge/example/melos_flutter_treezor_entrust_sdk_bridge_example.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/fonts/melos_fonts.iml" filepath="$PROJECT_DIR$/packages/fonts/melos_fonts.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/home/melos_home.iml" filepath="$PROJECT_DIR$/modules/home/melos_home.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/navigation/melos_navigation.iml" filepath="$PROJECT_DIR$/packages/navigation/melos_navigation.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/notifications/melos_notifications.iml" filepath="$PROJECT_DIR$/modules/notifications/melos_notifications.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/payments/melos_payments.iml" filepath="$PROJECT_DIR$/packages/payments/melos_payments.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/profile/melos_profile.iml" filepath="$PROJECT_DIR$/modules/profile/melos_profile.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/sca_treezor/melos_sca_treezor.iml" filepath="$PROJECT_DIR$/packages/sca_treezor/melos_sca_treezor.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/apps/mobile_app/melos_sf_app_platform.iml" filepath="$PROJECT_DIR$/apps/mobile_app/melos_sf_app_platform.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/sf_infrastructure/melos_sf_infrastructure.iml" filepath="$PROJECT_DIR$/packages/sf_infrastructure/melos_sf_infrastructure.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/sf_localizations/melos_sf_localizations.iml" filepath="$PROJECT_DIR$/packages/sf_localizations/melos_sf_localizations.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/sf_shared/melos_sf_shared.iml" filepath="$PROJECT_DIR$/packages/sf_shared/melos_sf_shared.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/modules/splash/melos_splash.iml" filepath="$PROJECT_DIR$/modules/splash/melos_splash.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/packages/utils/melos_utils.iml" filepath="$PROJECT_DIR$/packages/utils/melos_utils.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/melos_sf-app-platform.iml" filepath="$PROJECT_DIR$/melos_sf-app-platform.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
1
.idea/runConfigurations/melos_bootstrap.xml
generated
1
.idea/runConfigurations/melos_bootstrap.xml
generated
@@ -6,6 +6,7 @@
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos bootstrap" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
|
||||
1
.idea/runConfigurations/melos_clean.xml
generated
1
.idea/runConfigurations/melos_clean.xml
generated
@@ -6,6 +6,7 @@
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos clean" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Flutter Run -> 'flutter_treezor_entrust_sdk_bridge_example'" type="FlutterRunConfigurationType" factoryName="Flutter">
|
||||
<option name="filePath" value="$PROJECT_DIR$/packages/flutter_treezor_entrust_sdk_bridge/example/lib/main.dart" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Flutter Test -> 'auth'" type="FlutterTestConfigType" factoryName="Flutter Test">
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/auth/test" />
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/payment/modules/auth/test" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Flutter Test -> 'dashboard_shell'" type="FlutterTestConfigType" factoryName="Flutter Test">
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/dashboard_shell/test" />
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/payment/modules/dashboard_shell/test" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Flutter Test -> 'home'" type="FlutterTestConfigType" factoryName="Flutter Test">
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/home/test" />
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/payment/modules/home/test" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
7
.idea/runConfigurations/melos_flutter_test_legacy_auth.xml
generated
Normal file
7
.idea/runConfigurations/melos_flutter_test_legacy_auth.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Flutter Test -> 'legacy_auth'" type="FlutterTestConfigType" factoryName="Flutter Test">
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/legacy/modules/legacy_auth/test" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Flutter Test -> 'notifications'" type="FlutterTestConfigType" factoryName="Flutter Test">
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/notifications/test" />
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/payment/modules/notifications/test" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Flutter Test -> 'profile'" type="FlutterTestConfigType" factoryName="Flutter Test">
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/profile/test" />
|
||||
<option name="testDir" value="$PROJECT_DIR$/modules/payment/modules/profile/test" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_analyze.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_analyze.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'analyze'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run analyze" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_app_dev.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_app_dev.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'app:dev'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run app:dev" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_app_prod.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_app_prod.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'app:prod'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run app:prod" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_app_staging.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_app_staging.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'app:staging'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run app:staging" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_check_deps.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_check_deps.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'check-deps'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run check-deps" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_clean.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_clean.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'clean'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run clean" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_format.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_format.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'format'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run format" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_format_check.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_format_check.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'format:check'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run format:check" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_generate.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_generate.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'generate'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run generate" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_outdated.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_outdated.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'outdated'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run outdated" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
12
.idea/runConfigurations/melos_run_sync_deps.xml
generated
Normal file
12
.idea/runConfigurations/melos_run_sync_deps.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'sync-deps'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run sync-deps" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,11 +1,12 @@
|
||||
<!-- Generated by Melos -->
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Melos Run -> 'bootstrap'" type="ShConfigurationType">
|
||||
<configuration default="false" name="Melos Run -> 'test'" type="ShConfigurationType">
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="SCRIPT_TEXT" value="melos run bootstrap" />
|
||||
<option name="SCRIPT_TEXT" value="melos run test" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true"/>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
67
.vscode/launch.json
vendored
67
.vscode/launch.json
vendored
@@ -2,39 +2,88 @@
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
//
|
||||
// Configurations are split between (Legacy) and (Payment) variants.
|
||||
// (Legacy) is the default and matches historical behavior; (Payment)
|
||||
// boots straight into the Treezor wallet flow via APP_MODE=payment.
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "SF Development",
|
||||
"name": "SF Development (Legacy)",
|
||||
"cwd": "apps/mobile_app",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"program": "lib/main_development.dart",
|
||||
"toolArgs": [
|
||||
"--flavor",
|
||||
"development",
|
||||
"--dart-define-from-file=config/development.json"
|
||||
"--dart-define-from-file=config/development.json",
|
||||
"--dart-define=APP_MODE=legacy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SF Staging",
|
||||
"name": "SF Development (Payment)",
|
||||
"cwd": "apps/mobile_app",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"program": "lib/main_development.dart",
|
||||
"toolArgs": [
|
||||
"--flavor",
|
||||
"development",
|
||||
"--dart-define-from-file=config/development.json",
|
||||
"--dart-define=APP_MODE=payment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SF Staging (Legacy)",
|
||||
"cwd": "apps/mobile_app",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "lib/main_staging.dart",
|
||||
"toolArgs": [
|
||||
"--flavor",
|
||||
"staging",
|
||||
"--dart-define-from-file=config/staging.json"
|
||||
"--dart-define-from-file=config/staging.json",
|
||||
"--dart-define=APP_MODE=legacy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SF Production",
|
||||
"name": "SF Staging (Payment)",
|
||||
"cwd": "apps/mobile_app",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"program": "lib/main_staging.dart",
|
||||
"toolArgs": [
|
||||
"--flavor",
|
||||
"staging",
|
||||
"--dart-define-from-file=config/staging.json",
|
||||
"--dart-define=APP_MODE=payment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SF Production (Legacy)",
|
||||
"cwd": "apps/mobile_app",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "lib/main_production.dart",
|
||||
"toolArgs": [
|
||||
"--flavor",
|
||||
"production",
|
||||
"--dart-define-from-file=config/production.json"
|
||||
"--dart-define-from-file=config/production.json",
|
||||
"--dart-define=APP_MODE=legacy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SF Production (Payment)",
|
||||
"cwd": "apps/mobile_app",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "lib/main_production.dart",
|
||||
"toolArgs": [
|
||||
"--flavor",
|
||||
"production",
|
||||
"--dart-define-from-file=config/production.json",
|
||||
"--dart-define=APP_MODE=payment"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
195
Resumen_Integracion_Juphoon.md
Normal file
195
Resumen_Integracion_Juphoon.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Integración Videollamadas Juphoon — Resumen del progreso
|
||||
|
||||
## Contexto
|
||||
|
||||
SaveFamily S.L (Bizkaia) está integrando videollamadas y chat entre su app móvil Flutter y sus smartwatches infantiles (RTOS/Android), usando el SDK de Juphoon (`jc_sdk`).
|
||||
|
||||
**Actores:**
|
||||
- Grupo SaveFamily S.L — Cliente, dueño de la app y backend
|
||||
- Shenzhen i365-Tech Co., Limited (Jane Zhang, Carmen) — Fabricante hardware, intermediario comercial
|
||||
- Juphoon/JUQU (Allen) — Proveedor del SDK de videollamadas
|
||||
- SeTracker — Proveedor del firmware del reloj y servidores auxiliares
|
||||
|
||||
**Cotización aprobada: $8,835** (Integración $2,200 + Chat $2,950 + Cloud Photo Album $735 + Encryption $2,950)
|
||||
|
||||
---
|
||||
|
||||
## Lo que se hizo
|
||||
|
||||
### 1. Análisis de documentación (3 rondas)
|
||||
- **Ronda 1** (31-03-2026): 50 preguntas técnicas → 27/50 respondidas (54%)
|
||||
- **Ronda 2** (01-04-2026): 17 preguntas generales → 17/17 respondidas (calidad desigual)
|
||||
- **Ronda 3** (09-04-2026): Documentación oficial SDK recibida — Quickstart V1.1 Flutter (13 páginas), sequence diagrams, protocolo TCP, connection/mutual dialing process
|
||||
- Documentos generados: análisis completo, conclusiones, preguntas bilingües ES/EN, análisis cruzado de respuestas
|
||||
|
||||
### 2. Cuenta Juphoon Cloud creada (16-04-2026)
|
||||
- Consola: juphoon.com (+34)
|
||||
- App creada: "SaveFamily" (tipo IoT, escenario Smartwatch)
|
||||
- **AppKey:** `9efcf2d889dc8a0320925096`
|
||||
- **AppSecret:** `ui7pr73ggl5rr0gf01np` (solo backend)
|
||||
- **AES_KEY IoT:** `8e3637pG7E9144E0` (solo backend)
|
||||
- Token auth activado en consola
|
||||
|
||||
### 3. Paquete `packages/videocall_sdk/` creado
|
||||
Wrapper 100% del `jc_sdk` v2.16.5 con arquitectura sólida (patrón `sca_treezor` del monorepo):
|
||||
|
||||
- **7 servicios** cubriendo toda la API pública del SDK:
|
||||
- `VideocallClient` → JCClient (auth, login, logout, messaging)
|
||||
- `VideocallCallService` → JCCall (llamadas 1-to-1)
|
||||
- `VideocallDeviceService` → JCMediaDevice (cámara, mic, speaker)
|
||||
- `VideocallChannelService` → JCMediaChannel (llamadas grupales)
|
||||
- `VideocallPushService` → JCPush (push notifications)
|
||||
- `VideocallNetService` → JCNet (estado de red)
|
||||
- `VideocallLogService` → JCLog (logging)
|
||||
- **Constructor injection** (no singletons estáticos)
|
||||
- **GetIt module** (`videocallSdkModule(config)`)
|
||||
- **`VideocallSdkManager`** orquestador de inicialización (Client → Device → Call/Channel/Push)
|
||||
- **`VideocallSdkConfig`** abstracto para config por entorno
|
||||
- **Riverpod providers** + StreamProviders para UI reactiva
|
||||
- **Callbacks del SDK → Dart Streams**
|
||||
|
||||
### 4. Permisos nativos configurados
|
||||
- **Android:** RECORD_AUDIO, ACCESS_WIFI_STATE, MODIFY_AUDIO_SETTINGS, BLUETOOTH + uses-feature (camera, bluetooth) + ProGuard rules (juphoon, justalk)
|
||||
- **iOS:** NSMicrophoneUsageDescription, NSPhotoLibraryUsageDescription, NSCameraUsageDescription actualizado + Podfile GCC_PREPROCESSOR_DEFINITIONS (PERMISSION_CAMERA, PHOTOS, MICROPHONE)
|
||||
|
||||
### 5. AppKey configurado por entorno
|
||||
- `juphoonAppKey` en development.json, staging.json, production.json
|
||||
- `Environment.juphoonAppKey` via `String.fromEnvironment()`
|
||||
- `SaveFamilyVideocallConfig` implementa `VideocallSdkConfig`
|
||||
- `videocallSdkModule(config)` integrado en `init_app.dart`
|
||||
|
||||
### 6. Feature `videocall/` creada en device_management
|
||||
Feature completa siguiendo el patrón del monorepo (builder + domain + data + presentation):
|
||||
|
||||
**Domain:**
|
||||
- `videocall_error.dart` — enums de error/success/screenMode
|
||||
- `videocall_participant.dart` — entidad Freezed para participantes grupales
|
||||
- `videocall_signaling_repository.dart` — interface señalización backend
|
||||
|
||||
**Data:**
|
||||
- `videocall_signaling_datasource.dart` — interface
|
||||
- `videocall_signaling_datasource_impl.dart` — placeholder (TODO cuando backend dé spec)
|
||||
- `videocall_signaling_repository_impl.dart` — impl
|
||||
|
||||
**State:**
|
||||
- `videocall_view_state.dart` — Freezed state 1-to-1 (screenMode, sdk ready, mic/speaker/camera, canvas, error/success events)
|
||||
- `videocall_view_model.dart` — Notifier 1-to-1 (init, login, call, answer, hangup, mute, speaker, camera, streams del SDK)
|
||||
- `group_call_view_state.dart` — Freezed state grupal
|
||||
- `group_call_view_model.dart` — Notifier grupal (join, leave, participants, streams)
|
||||
|
||||
**Widgets:**
|
||||
- `video_view_widget.dart` — renderiza JCMediaDeviceVideoCanvas (iOS/Android)
|
||||
- `call_controls_widget.dart` — mic, speaker, camera, hangup (botones circulares)
|
||||
- `call_status_indicator.dart` — "Llamando...", "Conectando..."
|
||||
- `incoming_call_overlay.dart` — aceptar/rechazar llamada entrante (fullscreen)
|
||||
- `participant_tile_widget.dart` — tile individual con video + nombre
|
||||
- `participant_grid_widget.dart` — grid responsivo de participantes
|
||||
|
||||
**Screen:**
|
||||
- `videocall_screen.dart` — 4 modos: idle (input userID + botón llamar), outgoing (llamando...), incoming (overlay aceptar/rechazar), inCall (video fullscreen + PIP + controles)
|
||||
|
||||
**Routing:**
|
||||
- `videocall_builder.dart` — GoRouter builder
|
||||
- Ruta: `/legacy/dashboard/device_management/videocall`
|
||||
|
||||
### 7. Code review realizado
|
||||
Score: **6/10 — Request changes**
|
||||
|
||||
**Issues identificados (pendientes de corregir):**
|
||||
1. Hardcoded test credentials (`p_test1/test123`) en UI de producción → guardar con `kDebugMode`
|
||||
2. `_onCallItemRemove` llama async sin await → race condition
|
||||
3. Todos los errores mapean a `I18n.errorGeneric` → sin diferenciación para el usuario
|
||||
4. `videocall_screen.dart` (310 líneas) demasiado grande → extraer `_IdleView` y `_InCallView` a ficheros separados como `ConsumerWidget`
|
||||
5. `group_call_view_model.dart` es dead code (no lo consume ninguna screen)
|
||||
6. Signaling placeholder con `throw UnimplementedError` → cambiar a no-op
|
||||
7. `VideocallParticipant` (domain) expone tipo SDK (`JCMediaDeviceVideoCanvas`) → mover al ViewModel
|
||||
|
||||
---
|
||||
|
||||
## Dónde quedamos
|
||||
|
||||
- **Rama:** `feature/videocall-sdk-integration`
|
||||
- Los cambios del paquete `videocall_sdk` están **commiteados y pusheados** (3 commits)
|
||||
- Los cambios de la feature están en disco pero **sin commitear** (necesitan correcciones del code review)
|
||||
- `fusion-app` avanzó y revirtió algunos cambios compartidos (permisos, rutas) → hay que re-sincronizar
|
||||
|
||||
---
|
||||
|
||||
## Pendiente
|
||||
|
||||
### Correcciones del code review
|
||||
- [ ] Guardar test credentials con `kDebugMode`
|
||||
- [ ] Fix async race en `_onCallItemRemove`
|
||||
- [ ] Implementar mensajes de error diferenciados
|
||||
- [ ] Extraer `_IdleView` y `_InCallView` a ficheros separados
|
||||
- [ ] Integrar o excluir group call ViewModel
|
||||
- [ ] Cambiar signaling placeholder de throw a no-op
|
||||
- [ ] Remover SDK type de domain entity
|
||||
|
||||
### Pruebas APP↔APP (primera llamada real)
|
||||
- [ ] Login con 2 userIDs de prueba (`p_test1`, `p_test2`)
|
||||
- [ ] Videollamada entre dos teléfonos físicos
|
||||
- [ ] Probar incoming call, reject, hangup, mute, camera switch
|
||||
- [ ] Probar app cerrada en iOS (riesgo #1 — push/background)
|
||||
|
||||
### Integración con backend
|
||||
- [ ] Obtener API REST del backend SaveFamily para señalización
|
||||
- [ ] Definir formato userID con backend (`p_<cuenta>` + sanitización emails)
|
||||
- [ ] Implementar datasource de señalización
|
||||
|
||||
### Pruebas APP↔Reloj
|
||||
- [ ] Llamada APP → Reloj
|
||||
- [ ] Llamada Reloj → APP
|
||||
- [ ] Llamadas grupales
|
||||
|
||||
### Producción
|
||||
- [ ] Token auth (backend genera tokens con AppSecret)
|
||||
- [ ] AppKeys separadas por entorno
|
||||
- [ ] Push/background iOS (PushKit + CallKit si necesario)
|
||||
|
||||
---
|
||||
|
||||
## 3 riesgos abiertos antes del pago ($8,835)
|
||||
|
||||
| # | Riesgo | Estado |
|
||||
|---|---|---|
|
||||
| 1 | **Push/background iOS** — la doc no menciona FCM/APNs, CallKit ni ConnectionService. App cerrada = no recibe llamadas. Posible deal-breaker | ❌ Sin respuesta |
|
||||
| 2 | **GDPR sin DPA** — servidores UE pero sin DPA, sin control routing, datos de menores | ❌ Email enviado 01-04, sin respuesta |
|
||||
| 3 | **Chat sin spec** — $2,950 sin lista de features, "mira SeTracker2" | ❌ Sin spec |
|
||||
| + | **Encryption** — $2,950 pagados, cero documentación del módulo | ❌ Sin spec |
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura confirmada
|
||||
|
||||
```
|
||||
APP (Flutter + jc_sdk) ←→ Juphoon Cloud (solo media)
|
||||
↕ API REST
|
||||
Backend SaveFamily ←→ Backend i365/SeTracker ←→ Smartwatch (firmware + jrtc_* C API)
|
||||
↕ TCP plano
|
||||
```
|
||||
|
||||
- El "Server" del protocolo TCP es **i365**, NO Juphoon
|
||||
- Juphoon Cloud **solo rutea audio/video** (media plane)
|
||||
- La señalización (quién llama a quién) va por el backend
|
||||
|
||||
## Naming conventions (protocolo TCP)
|
||||
|
||||
| Tipo | Formato | Ejemplo |
|
||||
|---|---|---|
|
||||
| Watch userID | `w_` + IMEI | `w_000078932675810` |
|
||||
| Mobile userID | `p_` + APP account | `p_abc10086` |
|
||||
| Group room | `did` + `_group` | `0245423235_group` |
|
||||
| Single room | `did` + `_` + APP account | `0245423235_abc10086` |
|
||||
|
||||
`@` y `.` se reemplazan por `_` en room numbers y userIDs.
|
||||
|
||||
## Documentación de referencia
|
||||
|
||||
- Quickstart V1.1: `~/Downloads/Video call API_ Juphoon Flutter SDK quickstart V1.1.pdf`
|
||||
- TCP Protocol: `~/Downloads/Juphoon Video Call TCP Protocol.docx`
|
||||
- Connection process: `~/Downloads/video call connection process Rev2.docx`
|
||||
- Mutual dialing: `~/Downloads/video call mutual dialing process.docx`
|
||||
- Schematics: `~/Downloads/schematics _2025.03.26 (2)/`
|
||||
- pub.dev: https://pub.dev/packages/jc_sdk
|
||||
- Consola Juphoon: https://developer.juphoon.com
|
||||
@@ -3,6 +3,11 @@ import java.io.FileInputStream
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
// START: FlutterFire Configuration
|
||||
id("com.google.gms.google-services")
|
||||
id("com.google.firebase.firebase-perf")
|
||||
id("com.google.firebase.crashlytics")
|
||||
// END: FlutterFire Configuration
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
@@ -22,6 +27,9 @@ android {
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
// Required by flutter_local_notifications (and any future libs that
|
||||
// need Java 8+ APIs on older Android API levels).
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
@@ -90,3 +98,13 @@ android {
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
// Required by AntelopAwareMessagingService to forward FCM pushes to the
|
||||
// Antelop SDK. The Antelop AAR (com.entrust.antelop:antelop) is brought
|
||||
// in transitively through the flutter_treezor_entrust_sdk_bridge plugin.
|
||||
implementation(platform("com.google.firebase:firebase-bom:33.4.0"))
|
||||
implementation("com.google.firebase:firebase-messaging")
|
||||
implementation("com.entrust.antelop:antelop:2.6.4")
|
||||
}
|
||||
|
||||
48
apps/mobile_app/android/app/google-services.json
Normal file
48
apps/mobile_app/android/app/google-services.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "535646668726",
|
||||
"project_id": "sf-platform-pre",
|
||||
"storage_bucket": "sf-platform-pre.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:535646668726:android:c3a09d6c26f0cdf95e6317",
|
||||
"android_client_info": {
|
||||
"package_name": "com.savefamily.app.dev"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAzo8E_L6iUYWmK1BDFpNqRri1df6CqJiY"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:535646668726:android:b87245b807258e3e5e6317",
|
||||
"android_client_info": {
|
||||
"package_name": "com.savefamily.app.stag"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAzo8E_L6iUYWmK1BDFpNqRri1df6CqJiY"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -21,3 +21,11 @@
|
||||
-dontwarn com.huawei.hms.location.LocationServices
|
||||
-dontwarn com.huawei.hms.push.RemoteMessage
|
||||
-dontwarn com.huawei.hms.security.SecComponentInstallWizard
|
||||
|
||||
## Juphoon jc_sdk
|
||||
-dontwarn com.juphoon.*
|
||||
-keep class com.juphoon.**{*;}
|
||||
-dontwarn com.justalk.*
|
||||
-keep class com.justalk.**{*;}
|
||||
-keepattributes InnerClasses
|
||||
-keep class **.R$* {*;}
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:label="@string/app_name"
|
||||
@@ -33,6 +50,29 @@
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||
android:value="sf_default_channel" />
|
||||
|
||||
<!-- Wrap FCM with Antelop SDK forwarding (see AntelopAwareMessagingService). -->
|
||||
<service
|
||||
android:name=".AntelopAwareMessagingService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!-- Disable Antelop's stock FCM service so AntelopAwareMessagingService is the only handler. -->
|
||||
<service
|
||||
android:name="fr.antelop.exposed.DefaultAntelopFirebaseMessagingService"
|
||||
tools:node="remove" />
|
||||
|
||||
<!-- Disable the firebase_messaging plugin's FCM service so AntelopAwareMessagingService is the only handler. -->
|
||||
<service
|
||||
android:name="io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingService"
|
||||
tools:node="remove" />
|
||||
</application>
|
||||
|
||||
<!-- Required to query activities that can process text, see:
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.savefamily.app
|
||||
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import fr.antelop.sdk.firebase.AntelopFirebaseMessagingUtil
|
||||
import io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingService
|
||||
|
||||
/**
|
||||
* FCM service that gives the Antelop SDK first dibs on every push, then
|
||||
* delegates the rest to the firebase_messaging Flutter plugin so Dart still
|
||||
* receives the notifications it expects.
|
||||
*
|
||||
* Without this, only one FirebaseMessagingService can win the
|
||||
* com.google.firebase.MESSAGING_EVENT intent — and once we added the
|
||||
* firebase_messaging plugin, its FlutterFirebaseMessagingService started
|
||||
* winning over Antelop's DefaultAntelopFirebaseMessagingService, leaving the
|
||||
* SDK forever waiting for activation pushes that never reached it.
|
||||
*/
|
||||
class AntelopAwareMessagingService : FlutterFirebaseMessagingService() {
|
||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||
val handled = AntelopFirebaseMessagingUtil.onMessageReceived(
|
||||
applicationContext,
|
||||
remoteMessage,
|
||||
)
|
||||
if (!handled) {
|
||||
super.onMessageReceived(remoteMessage)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
super.onNewToken(token)
|
||||
AntelopFirebaseMessagingUtil.onTokenRefresh(applicationContext)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "950566980029",
|
||||
"project_id": "sf-platform-pro",
|
||||
"storage_bucket": "sf-platform-pro.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:950566980029:android:75a7c10b6259d09681aad4",
|
||||
"android_client_info": {
|
||||
"package_name": "com.savefamily.app"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDkjNdOAK0ype7wgdgiC1BCKV_pP4s_mlA"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,6 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
# permission_handler: enable contacts permission
|
||||
PERMISSION_CONTACTS=1
|
||||
|
||||
@@ -21,6 +21,9 @@ plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.9.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||
id("com.google.gms.google-services") version "4.4.2" apply false
|
||||
id("com.google.firebase.firebase-perf") version "1.4.2" apply false
|
||||
id("com.google.firebase.crashlytics") version "3.0.2" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
apps/mobile_app/assets/shared/images/gps_location.png
Normal file
BIN
apps/mobile_app/assets/shared/images/gps_location.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
apps/mobile_app/assets/shared/images/iso_sf.png
Normal file
BIN
apps/mobile_app/assets/shared/images/iso_sf.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
19
apps/mobile_app/assets/shared/images/location.svg
Normal file
19
apps/mobile_app/assets/shared/images/location.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="43" height="55" viewBox="0 0 43 55" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_973)">
|
||||
<path d="M22.339 0C23.8812 0.269461 25.4552 0.434132 26.9498 0.838323C31.1949 1.96108 34.6769 4.20659 37.4434 7.41018C39.4944 9.79042 40.9094 12.5 41.5931 15.494C42.7856 20.7335 41.9906 25.7186 38.9379 30.2545C33.6752 38.0988 28.3012 45.8982 22.9749 53.7126C22.8954 53.8323 22.816 53.9521 22.7365 54.0718C21.8143 55.3293 20.2721 55.3293 19.3817 54.0569C18.38 52.6347 17.4261 51.1976 16.4562 49.7605C12.068 43.3084 7.64795 36.8713 3.29152 30.4042C0.429637 26.1527 -0.572024 21.512 0.334241 16.5569C1.81288 8.36826 8.20443 2.15569 16.8378 0.404192C17.8077 0.209581 18.8093 0.134731 19.7951 0C20.6377 0 21.4963 0 22.339 0Z" fill="#F9B03C"/>
|
||||
<path d="M28.8576 25.8982L31.5923 28.473C32.3396 29.1766 32.3396 30.3293 31.5923 31.0479C30.8451 31.7515 29.6208 31.7515 28.8576 31.0479L26.123 28.473C25.3757 27.7694 25.3757 26.6167 26.123 25.8982C26.8702 25.1796 28.0945 25.1796 28.8576 25.8982Z" fill="white"/>
|
||||
<path d="M12.926 20.7485C12.926 21.7515 12.0675 22.5599 11.0022 22.5599H7.13867C6.07341 22.5599 5.21484 21.7515 5.21484 20.7485C5.21484 19.7455 6.07341 18.9371 7.13867 18.9371H11.0022C12.0675 18.9371 12.926 19.7455 12.926 20.7485Z" fill="white"/>
|
||||
<path d="M12.4651 15.5987L9.73037 13.0388C8.9831 12.3352 8.9831 11.1825 9.73037 10.464C10.4776 9.76038 11.7019 9.76038 12.4651 10.464L15.1998 13.0388C15.947 13.7424 15.947 14.8951 15.1998 15.6137C14.4366 16.3173 13.2123 16.3173 12.4651 15.5987Z" fill="white"/>
|
||||
<path d="M12.4651 31.0329C11.7178 31.7365 10.4935 31.7365 9.73037 31.0329C8.9831 30.3293 8.9831 29.1766 9.73037 28.458L12.4651 25.8832C13.2123 25.1796 14.4366 25.1796 15.1998 25.8832C15.947 26.5868 15.947 27.7394 15.1998 28.458L12.4651 31.0329Z" fill="white"/>
|
||||
<path d="M20.6533 13.473C19.5881 13.473 18.7295 12.6646 18.7295 11.6616V8.0239C18.7295 7.02091 19.5881 6.21252 20.6533 6.21252C21.7186 6.21252 22.5771 7.02091 22.5771 8.0239V11.6616C22.593 12.6646 21.7186 13.473 20.6533 13.473Z" fill="white"/>
|
||||
<path d="M20.6533 35.2993C19.5881 35.2993 18.7295 34.4909 18.7295 33.4879V29.8502C18.7295 28.8472 19.5881 28.0388 20.6533 28.0388C21.7186 28.0388 22.5771 28.8472 22.5771 29.8502V33.4879C22.593 34.4909 21.7186 35.2993 20.6533 35.2993Z" fill="white"/>
|
||||
<path d="M28.8576 15.5988C28.1104 16.3024 26.8861 16.3024 26.123 15.5988C25.3757 14.8952 25.3757 13.7425 26.123 13.0239L28.8576 10.4491C29.6049 9.74549 30.8292 9.74549 31.5923 10.4491C32.3396 11.1527 32.3396 12.3054 31.5923 13.0239L28.8576 15.5988Z" fill="white"/>
|
||||
<path d="M34.1839 22.5748H30.3203C29.255 22.5748 28.3965 21.7664 28.3965 20.7634C28.3965 19.7604 29.255 18.952 30.3203 18.952H34.1839C35.2491 18.952 36.1077 19.7604 36.1077 20.7634C36.1077 21.7514 35.2491 22.5748 34.1839 22.5748Z" fill="white"/>
|
||||
<path d="M20.1288 15.7634C20.256 15.6587 20.3991 15.5539 20.574 15.5389C21.0033 15.509 21.21 16.003 21.2895 16.4072C21.4485 17.2155 21.5916 18.0389 21.7506 18.8473C21.7824 19.0269 21.8301 19.2215 21.9891 19.3263C22.1322 19.4161 22.3389 19.4012 22.5138 19.3712C23.5472 19.2664 24.6284 19.476 25.5187 19.985C25.8049 20.1497 26.0752 20.3593 26.1229 20.6736C26.1706 21.1227 25.6777 21.4371 25.2166 21.5868C24.4058 21.8563 23.5154 21.9161 22.6727 21.7664C22.9589 22.2904 23.1815 22.8593 23.3564 23.4281C23.5631 24.1467 23.5154 25.1197 22.784 25.3892C22.2276 25.5838 21.6234 25.2245 21.2577 24.7904C20.892 24.3563 20.6376 23.8323 20.1765 23.473C19.5406 24.1916 18.5389 24.5359 17.5849 24.7904C16.9013 24.97 16.0268 25.0299 15.6452 24.4611C15.4544 24.1616 15.8519 23.2335 16.3766 22.8443C16.9649 22.4102 17.2829 21.991 17.887 21.6916C16.5674 21.2275 15.8996 21.0479 15.0569 19.985C14.8185 19.6856 14.6595 19.2066 14.9933 19.0119C15.1046 18.9521 15.2318 18.9521 15.3431 18.9521C16.4879 18.9221 17.7439 19.3114 18.5071 18.488C19.2067 17.7096 19.2067 16.5419 20.1288 15.7634Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_973">
|
||||
<rect width="42.1333" height="55" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
12
apps/mobile_app/assets/shared/images/profile.svg
Normal file
12
apps/mobile_app/assets/shared/images/profile.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M108.055 98.4048L117.431 107.744C119.993 110.297 119.993 114.478 117.431 117.084C114.869 119.636 110.672 119.636 108.055 117.084L98.6799 107.744C96.1179 105.192 96.1179 101.011 98.6799 98.4048C101.242 95.7983 105.439 95.7983 108.055 98.4048Z" fill="#588EA5"/>
|
||||
<path d="M53.4371 79.7255C53.4371 83.3636 50.4936 86.2958 46.8414 86.2958H33.5956C29.9435 86.2958 27 83.3636 27 79.7255C27 76.0874 29.9435 73.1552 33.5956 73.1552H46.8414C50.4936 73.1552 53.4371 76.0874 53.4371 79.7255Z" fill="#588EA5"/>
|
||||
<path d="M51.8563 61.0462L42.4807 51.7609C39.9187 49.2087 39.9187 45.0276 42.4807 42.4212C45.0426 39.8691 49.2398 39.8691 51.8563 42.4212L61.2319 51.7609C63.7938 54.313 63.7938 58.4941 61.2319 61.1005C58.6155 63.6526 54.4182 63.6526 51.8563 61.0462Z" fill="#588EA5"/>
|
||||
<path d="M51.8563 117.03C49.2943 119.582 45.0971 119.582 42.4807 117.03C39.9187 114.478 39.9187 110.297 42.4807 107.69L51.8563 98.3505C54.4182 95.7984 58.6155 95.7984 61.2319 98.3505C63.7938 100.903 63.7938 105.084 61.2319 107.69L51.8563 117.03Z" fill="#588EA5"/>
|
||||
<path d="M79.9287 53.3356C76.2765 53.3356 73.333 50.4034 73.333 46.7653V33.5703C73.333 29.9322 76.2765 27 79.9287 27C83.5808 27 86.5243 29.9322 86.5243 33.5703V46.7653C86.5788 50.4034 83.5808 53.3356 79.9287 53.3356Z" fill="#588EA5"/>
|
||||
<path d="M79.9287 132.505C76.2765 132.505 73.333 129.573 73.333 125.935V112.74C73.333 109.102 76.2765 106.17 79.9287 106.17C83.5808 106.17 86.5243 109.102 86.5243 112.74V125.935C86.5788 129.573 83.5808 132.505 79.9287 132.505Z" fill="#588EA5"/>
|
||||
<path d="M108.055 61.0462C105.494 63.5983 101.296 63.5983 98.6799 61.0462C96.1179 58.4941 96.1179 54.313 98.6799 51.7066L108.055 42.367C110.617 39.8149 114.815 39.8149 117.431 42.367C119.993 44.9191 119.993 49.1002 117.431 51.7066L108.055 61.0462Z" fill="#588EA5"/>
|
||||
<path d="M126.316 86.3501H113.07C109.418 86.3501 106.475 83.4179 106.475 79.7798C106.475 76.1417 109.418 73.2095 113.07 73.2095H126.316C129.968 73.2095 132.912 76.1417 132.912 79.7798C132.912 83.3636 129.968 86.3501 126.316 86.3501Z" fill="#588EA5"/>
|
||||
<path d="M78.1299 61.6435C78.5659 61.2634 79.0565 60.8833 79.6561 60.829C81.1279 60.7204 81.8365 62.5124 82.109 63.9785C82.6541 66.9107 83.1447 69.8972 83.6898 72.8294C83.7988 73.481 83.9624 74.1869 84.5075 74.567C84.998 74.8928 85.7067 74.8385 86.3063 74.7299C89.8494 74.3498 93.556 75.11 96.6085 76.9562C97.5897 77.5535 98.5164 78.3137 98.6799 79.454C98.8434 81.083 97.1536 82.2233 95.5729 82.7663C92.7929 83.7437 89.7404 83.9609 86.8514 83.4179C87.8325 85.3184 88.5957 87.3818 89.1953 89.4452C89.9039 92.0517 89.7404 95.5812 87.2329 96.5586C85.3251 97.2645 83.2537 95.9613 82 94.3866C80.7463 92.8119 79.8742 90.9114 78.2934 89.6082C76.113 92.2146 72.6789 93.4635 69.4083 94.3866C67.0644 95.0382 64.0664 95.2554 62.7582 93.192C62.1041 92.106 63.4668 88.7394 65.2656 87.3275C67.2825 85.7528 68.3727 84.2324 70.444 83.1464C65.9197 81.4631 63.6303 80.8115 60.7413 76.9562C59.9237 75.8702 59.3786 74.1326 60.5233 73.4267C60.9049 73.2095 61.3409 73.2095 61.7225 73.2095C65.6472 73.1009 69.9534 74.5127 72.5699 71.5262C74.9683 68.7026 74.9683 64.4672 78.1299 61.6435Z" fill="#588EA5"/>
|
||||
<circle cx="80" cy="80" r="78" stroke="#588EA5" stroke-width="4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"env": "development",
|
||||
"apiBaseUrl": "https://api-neki-b2b.neki.es/gateway/api/",
|
||||
"apiOrigin": "https://neki-b2b.neki.es"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"env": "production",
|
||||
"apiBaseUrl": "https://api-neki-b2b.neki.es/gateway/api/",
|
||||
"apiOrigin": "https://neki-b2b.neki.es"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"env": "staging",
|
||||
"apiBaseUrl": "https://api-neki-b2b.neki.es/gateway/api/",
|
||||
"apiOrigin": "https://neki-b2b.neki.es"
|
||||
}
|
||||
637
apps/mobile_app/docs/analytics-catalog-technical.md
Normal file
637
apps/mobile_app/docs/analytics-catalog-technical.md
Normal file
@@ -0,0 +1,637 @@
|
||||
# Catálogo de Analíticas — SaveFamily (módulo legacy)
|
||||
|
||||
> Documento para el equipo de Marketing. Describe cada evento de Firebase
|
||||
> Analytics que la app envía desde el módulo legacy: qué significa, cuándo se
|
||||
> dispara, qué parámetros trae, y qué insight ofrece.
|
||||
>
|
||||
> **Ambiente:** Eventos visibles en vivo en Firebase Console Analytics
|
||||
> **DebugView** (para builds debug/profile con el flag de debug activado).
|
||||
> Los reportes históricos están en **Realtime**, **Engagement Events** y
|
||||
> **Engagement Pages and screens**.
|
||||
>
|
||||
> **Parámetro común:** Cada evento incluye automáticamente un parámetro
|
||||
> `consent_status` (`true` / `false`) para permitir filtrado por
|
||||
> consentimiento GDPR cuando corresponda.
|
||||
|
||||
---
|
||||
|
||||
## Índice
|
||||
|
||||
1. [User Properties (propiedades del usuario)](#user-properties)
|
||||
2. [Screen Views (vistas de pantalla automáticas)](#screen-views)
|
||||
3. [Autenticación (`legacy_auth_*`)](#autenticación)
|
||||
4. [Cuenta (`legacy_account_*`)](#cuenta)
|
||||
5. [Dispositivo — Setup / alta (`legacy_device_setup_*`)](#dispositivo--setup)
|
||||
6. [Dispositivo — Funciones (`legacy_device_*`)](#dispositivo--funciones)
|
||||
7. [Contactos del dispositivo (`legacy_contacts_*`)](#contactos-del-dispositivo)
|
||||
8. [Ajustes (`legacy_settings_*`)](#ajustes)
|
||||
9. [Soporte (`legacy_support_*`)](#soporte)
|
||||
10. [Onboarding (`legacy_onboarding_*`)](#onboarding)
|
||||
11. [Panel principal (`legacy_control_panel_*`)](#panel-principal)
|
||||
12. [Ubicación y mapa (`legacy_location_*`)](#ubicación-y-mapa)
|
||||
|
||||
---
|
||||
|
||||
## User Properties
|
||||
|
||||
Son propiedades que se setean una sola vez por usuario (al hacer login) y
|
||||
sirven para **segmentar** a los usuarios en los reportes. Cualquier evento
|
||||
puede cruzarse por estas dimensiones en Firebase Analytics.
|
||||
|
||||
| Propiedad | Descripción | Valores ejemplo | Cuándo se setea |
|
||||
|---|---|---|---|
|
||||
| `env` | Ambiente de la app | `development`, `staging`, `production` | Al arrancar la app |
|
||||
| `user_id` (interna) | Identificador único del usuario | UUID del backend | Al confirmar el login (después del 2FA) |
|
||||
| `user_role` | Rol del usuario en el backend | `client`, `admin`, etc. | Al login |
|
||||
| `user_language` | Idioma preferido del usuario | `es`, `en`, `fr`, `de`, `it`, `pt` | Al login |
|
||||
| `user_signup_date` | Fecha de creación de la cuenta (ISO 8601 UTC) | `2024-04-07T10:34:42.000Z` | Al login |
|
||||
| `user_has_phone` | Si tiene teléfono registrado | `true` / `false` | Al login |
|
||||
| `user_has_api_key` | Si tiene una API key asignada (usuario técnico) | `true` / `false` | Al login |
|
||||
|
||||
> **Nota futura:** Cuando se lance el plan premium, se agregará
|
||||
> `user_plan` (`free` / `premium` / `family`) para segmentar la base por
|
||||
> plan.
|
||||
|
||||
---
|
||||
|
||||
## Screen Views
|
||||
|
||||
Cada vez que el usuario navega a una pantalla, Firebase recibe un evento
|
||||
automático `screen_view` con el parámetro `screen_name` igual al nombre
|
||||
lógico de la ruta (no el nombre de clase Flutter).
|
||||
|
||||
**Esto se captura automáticamente**, sin instrumentación manual en cada
|
||||
pantalla, mediante un listener del router. **También captura los cambios de
|
||||
tab del bottom navigation** (home device functions mapa chat).
|
||||
|
||||
### Pantallas del módulo legacy que se trackean
|
||||
|
||||
| Screen name | Pantalla |
|
||||
|---|---|
|
||||
| `splash` | Pantalla de carga inicial |
|
||||
| `legacy_onboarding` | Intro/onboarding |
|
||||
| `legacy_login` | Pantalla de login |
|
||||
| `legacy_signup` | Alta de cuenta |
|
||||
| `legacy_recover_password` | Recuperación de contraseña |
|
||||
| `legacy_device_setup` | Wizard de alta de reloj/dispositivo |
|
||||
| `legacy_request_link_phone` | Inicio de vinculación de teléfono |
|
||||
| `legacy_verify_link_phone_code` | Verificación del código OTP |
|
||||
| `control_panel` | Dashboard principal (home del legacy) |
|
||||
| `customer_service` | Pantalla de soporte |
|
||||
| `account_settings` | Menú de cuenta |
|
||||
| `personal_data` | Editar datos personales |
|
||||
| `change_password` | Cambiar contraseña |
|
||||
| `linked_devices` | Dispositivos vinculados a la cuenta |
|
||||
| `app_users` | Sub-usuarios de la app |
|
||||
| `delete_account` | Flujo de eliminación de cuenta |
|
||||
| `device_management` | Menú de gestión del dispositivo |
|
||||
| `scheduled_activities` | Actividades programadas |
|
||||
| `contacts` | Contactos del dispositivo |
|
||||
| `edit_contact` | Editar un contacto |
|
||||
| `health` | Salud (ritmo cardíaco, SpO2) |
|
||||
| `remote_connection` | Conexión remota (cámara, llamada) |
|
||||
| `locate_device` | Localizar dispositivo |
|
||||
| `rewards` | Recompensas |
|
||||
| `activity_meter` | Medidor de actividad (pasos) |
|
||||
| `apps_use` | Uso de apps |
|
||||
| `volume_control` | Control de volumen |
|
||||
| `call_history` | Historial de llamadas |
|
||||
| `background_image` | Imagen de fondo del dispositivo |
|
||||
| `legacy_location` | Mapa de ubicación |
|
||||
| `legacy_chat` | Chat (placeholder) |
|
||||
| `settings` | Menú de ajustes |
|
||||
| `alarm` | Alarmas |
|
||||
| `remote_management` | Gestión remota |
|
||||
| `sos_agenda` | Contactos SOS |
|
||||
| `sound` | Sonido del dispositivo |
|
||||
| `sync_clock` | Sincronización de reloj |
|
||||
| `app_store` | Gestión de apps instaladas |
|
||||
| `battery` | Batería / modo nocturno |
|
||||
| `block_phone` | Bloqueo de teléfono (whitelist) |
|
||||
| `disable_functions` | Desactivar funciones (teclado, GPS) |
|
||||
| `language` | Idioma del dispositivo |
|
||||
| `legacy_notifications` | Notificaciones del dispositivo |
|
||||
| `remote_on_off` | Encendido/apagado remoto |
|
||||
| `alerts` | Alertas |
|
||||
| `timezone` | Zona horaria |
|
||||
| `wifi_settings` | Configuración WiFi |
|
||||
|
||||
**Insight para marketing:** Con estas screen_view podés construir funnels
|
||||
(ej: `legacy_login control_panel device_management locate_device`) y
|
||||
medir tiempos entre pantallas, rebotes y pantallas más visitadas.
|
||||
|
||||
---
|
||||
|
||||
## Autenticación
|
||||
|
||||
Prefijo `legacy_auth_*` — cubre login, 2FA, signup, recuperación de
|
||||
contraseña, vinculación de teléfono y logout.
|
||||
|
||||
### Login
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_login_attempt` | El usuario pulsa "Iniciar sesión" después de validar el formulario en el cliente. | — | Tope del funnel de login. Usar como base del "100 %" del funnel. |
|
||||
| `legacy_auth_login_success` | El backend aceptó email + contraseña. Aún falta el 2FA. | — | Credenciales válidas. Usar para medir la calidad de la contraseña/email. |
|
||||
| `legacy_auth_login_failure` | El backend rechazó las credenciales o hubo un error de red. | `reason` (mensaje de error) | Fricción. Analizar los `reason` más frecuentes para detectar problemas. |
|
||||
|
||||
### 2FA (doble factor)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_2fa_requested` | El backend envió el código 2FA al usuario. | — | Usuario pasó el primer paso del login. |
|
||||
| `legacy_auth_2fa_verified` | El código 2FA fue verificado y la sesión está activa. | — | Login exitoso. Fin del funnel de login. |
|
||||
| `legacy_auth_2fa_failure` | El código 2FA fue rechazado (incorrecto, expirado). | `reason` | Fricción en el 2FA. Si es muy alto, puede indicar problemas con la entrega del código. |
|
||||
| `legacy_auth_2fa_resend` | El usuario pidió reenviar el código. | — | Indica que no le llegó el primero. Útil para medir problemas de entrega. |
|
||||
|
||||
### Signup
|
||||
|
||||
El signup es un wizard de **2 pasos** (`step_index` 0, 1):
|
||||
|
||||
- **Paso 0 — Datos personales:** nombre, apellido, email, teléfono (con picker de país), aceptación de términos.
|
||||
- **Paso 1 — Contraseña:** password y repeat password con validación de reglas.
|
||||
|
||||
El `language` se infiere del locale del dispositivo al momento del submit.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_signup_started` | El usuario envió el formulario final (submit del paso 1). | — | Top del funnel de request al backend. |
|
||||
| `legacy_auth_signup_completed` | El backend creó la cuenta exitosamente. | — | Conversión de nueva cuenta. |
|
||||
| `legacy_auth_signup_failed` | Error en el alta (email ya existe, datos inválidos, error de red). | `reason` | Drop-off del signup. Analizar `reason` para ver si hay patrones. |
|
||||
| `legacy_auth_signup_step_completed` | El usuario avanzó a un paso siguiente (validación pasó). | `step_index` (0, 1) — el paso que JUSTO terminó | Funnel interno del signup. Permite ver cuántos completan paso 0 y llegan al 1. |
|
||||
| `legacy_auth_signup_step_back` | El usuario tocó "atrás" dentro del wizard. | `step_index` — el paso del que vuelve | Indica que el usuario quiere corregir algo — fricción. |
|
||||
| `legacy_auth_signup_step_validation_failed` | El usuario tocó "siguiente" pero la validación del formulario lo rechazó. | `step_index` | **Muy valioso:** dice en qué paso hay más problemas de validación. Si step 0 falla: nombre/apellido, email, teléfono o términos. Si step 1 falla: reglas de contraseña. |
|
||||
|
||||
### Recuperación de contraseña
|
||||
|
||||
Flujo **exclusivo por email** (se removió la opción de SMS/teléfono).
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_password_reset_requested` | El usuario inició el flujo de recuperar contraseña tipeando su email. | — | Fricción: alguien no recuerda su contraseña. |
|
||||
| `legacy_auth_password_reset_email_sent` | El backend confirmó el envío del email de recuperación. | — | Confirma que el email salió. Cruzar con `reset_requested` para medir fallas. |
|
||||
| `legacy_auth_password_reset_completed` | El usuario guardó la nueva contraseña exitosamente. | — | **Conversión final** del funnel de recuperación. |
|
||||
| `legacy_auth_password_reset_failed` | Error al intentar guardar la nueva contraseña. | `reason` (`unequal_passwords`, `too_short`, `no_capitals`, `no_numbers`, `no_special_chars`, o mensaje del backend) | Permite ver qué reglas de validación molestan más a los usuarios. |
|
||||
|
||||
### Vinculación de teléfono
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_link_phone_code_requested` | El usuario envió su número y pidió el código OTP. | — | Inicio del flujo de linking. |
|
||||
| `legacy_auth_link_phone_code_request_failed` | Falló el pedido del código al backend. | `reason` | Fricción inicial. |
|
||||
| `legacy_auth_link_phone_code_verified` | El código OTP fue verificado con éxito. | — | Número vinculado. |
|
||||
| `legacy_auth_link_phone_code_verification_failed` | Falló la verificación (código incorrecto o expirado). | `reason` | Fricción en el segundo paso. |
|
||||
|
||||
### Logout
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_auth_logout` | El usuario cerró sesión y la app limpió la sesión local. | — | Señal de fin de sesión. Cruzar con duración de sesión para ver patrones de uso. |
|
||||
|
||||
---
|
||||
|
||||
## Cuenta
|
||||
|
||||
Prefijo `legacy_account_*` — cubre edición de perfil, contraseña,
|
||||
dispositivos vinculados, usuarios de la app y **eliminación de cuenta
|
||||
(señal crítica de churn)**.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_account_personal_data_edited` | El usuario guardó cambios en sus datos personales (nombre, apellido, teléfono). | — | Engagement con la cuenta. |
|
||||
| `legacy_account_password_changed` | Cambio de contraseña exitoso. | — | Señal de buen hábito de seguridad. |
|
||||
| `legacy_account_password_change_failed` | El cambio de contraseña falló. | `reason` | Fricción. |
|
||||
| `legacy_account_linked_device_unlinked` | El usuario quitó un dispositivo vinculado de su cuenta. | — | Posible señal temprana de desuso del dispositivo. |
|
||||
| `legacy_account_linked_device_renamed` | El usuario renombró un dispositivo vinculado (editó el carrier name). | — | Personalización / engagement con la gestión de dispositivos. |
|
||||
| `legacy_account_app_user_delete_triggered` | El usuario tocó "eliminar" en la pantalla de app users. | — | Nota técnica: la implementación actual borra al usuario logueado (parece ser placeholder). El evento se mantiene para medir demanda del feature. |
|
||||
| `legacy_account_deletion_initiated` | **CHURN SIGNAL** — El usuario entró al flujo "Eliminar cuenta". | — | Top del funnel de churn. |
|
||||
| `legacy_account_deletion_confirmed` | El usuario confirmó la eliminación y la API call está en progreso. | — | El usuario quiere realmente irse. |
|
||||
| `legacy_account_deletion_completed` | El backend confirmó la eliminación. | — | Usuario perdido. |
|
||||
| `legacy_account_deletion_cancelled` | El usuario canceló antes de confirmar la eliminación. | — | Save: el usuario se arrepintió. Útil para medir efectividad de pantallas de retención. |
|
||||
|
||||
---
|
||||
|
||||
## Dispositivo — Setup
|
||||
|
||||
Prefijo `legacy_device_setup_*` — **el momento aha del producto**: vincular
|
||||
un reloj/dispositivo del hijo a la cuenta del padre/madre.
|
||||
|
||||
### Funnel del wizard
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_setup_started` | El usuario entró al wizard de alta de dispositivo. | — | Top del funnel de activación. |
|
||||
| `legacy_device_setup_step_completed` | El usuario completó un paso del wizard. | `step` (`intro`, `link_info`, `scan_watch`, `profile`), `duration_seconds` (cuánto tardó en ese paso) | Permite ver dónde se abandona más el wizard **Y cuánto tiempo pasan los usuarios en cada paso** — fricción directa. |
|
||||
| `legacy_device_setup_completed` | El dispositivo se creó exitosamente y está vinculado. | `child_gender` (M/F/other), `relation_type` (mother/father/etc), `child_age_years` | **Conversión de activación + demográficos del usuario final**. Marketing puede construir **personas reales** con estos 3 params: género, edad y relación con el adulto que compró. |
|
||||
| `legacy_device_setup_failed` | Falló un paso del wizard. | `at_step` (en qué paso falló), `reason` (error) | Señal para el equipo técnico de dónde hay problemas. |
|
||||
| `legacy_device_setup_cancelled` | El usuario volvió atrás y abandonó el wizard. | `at_step` | Drop-off del wizard. |
|
||||
|
||||
### Entrada del código del reloj (QR vs. manual)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_setup_qr_scanned` | El usuario escaneó exitosamente el código QR del reloj. | — | Método "rápido". Si su ratio baja, el QR scanner puede estar fallando. |
|
||||
| `legacy_device_setup_manual_code_entered` | El usuario avanzó con el código tipeado manualmente (no escaneó). | — | Fallback. Si crece mucho el ratio vs QR, invertir en mejorar la UX del scanner. |
|
||||
|
||||
### Familias con múltiples hijos
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_setup_reset_for_new_kid` | Después de terminar un alta, el usuario tocó "agregar otro hijo". | — | **Señal de familia con múltiples hijos**. Estos usuarios típicamente tienen mayor retention y LTV — son el mejor segmento. |
|
||||
|
||||
---
|
||||
|
||||
## Dispositivo — Funciones
|
||||
|
||||
Prefijo `legacy_device_*` — acciones sobre el dispositivo ya vinculado.
|
||||
Mide qué features del producto se usan más.
|
||||
|
||||
### Localización del dispositivo (comando "find")
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_locate_requested` | El usuario pulsó el botón de localizar (intento). | — | Uso del feature principal del producto. Top del mini-funnel de localización. |
|
||||
| `legacy_device_locate_success` | El comando de localizar fue enviado con éxito al backend. | — | El dispositivo va a sonar. Conversión del mini-funnel. |
|
||||
| `legacy_device_locate_failure` | El comando de localizar falló (error del backend o de red). | `reason` | Problema técnico al localizar. Drop-off del mini-funnel. |
|
||||
|
||||
### Conexión remota (cámara + llamadas)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_remote_connection_started` | El usuario entró a la pantalla de conexión remota. | — | Intención de interactuar remotamente. |
|
||||
| `legacy_device_remote_connection_photo_taken` | El usuario pidió una foto de la cámara remota. | — | Feature avanzada. Permite medir uso de la cámara del reloj. |
|
||||
| `legacy_device_remote_connection_call_initiated` | El usuario inició una llamada bidireccional. | — | Feature crítica: llamar al niño. |
|
||||
| `legacy_device_remote_connection_picture_viewed` | El usuario navegó entre fotos de la cámara remota. | `direction` (`next`, `prev`, `direct`) | Engagement con la galería: cuántas fotos revisa el padre después de pedirlas. |
|
||||
|
||||
### Volumen del dispositivo
|
||||
|
||||
Cada envío del formulario dispara **un evento por tipo de volumen que
|
||||
efectivamente cambió** (media, ringtone, alarm) — si el usuario movió solo
|
||||
el media, solo se manda ese. Permite medir qué tipo de sonido configuran
|
||||
más los padres.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_volume_control_changed` | El usuario guardó un cambio de volumen en el dispositivo, se emite 1 vez por cada tipo modificado. | `type` (`media`, `ringtone`, `alarm`), `level` (0-100) | Configuración. Cruzar `type` para ver cuál se ajusta más. |
|
||||
|
||||
### Imagen de fondo del reloj
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_background_image_changed` | El usuario seleccionó una imagen existente como fondo. | — | Personalización. |
|
||||
| `legacy_device_background_image_uploaded` | El usuario subió una foto personal como fondo. | — | Alta personalización — indicador de engagement. |
|
||||
|
||||
### Actividades programadas (alarmas personalizadas del dispositivo)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_scheduled_activity_added` | El usuario agregó una actividad programada. | `week_day` (0-6, 0 = domingo), `period` (`HH:mm-HH:mm`) | **Dato muy útil:** permite ver qué horarios programan los padres (desayuno, colegio, deberes, etc) y qué días. |
|
||||
| `legacy_device_scheduled_activity_updated` | El usuario editó una actividad programada. | `week_day`, `period` | Refinamiento de configuración. |
|
||||
| `legacy_device_scheduled_activity_removed` | El usuario eliminó una actividad programada. | — | |
|
||||
|
||||
### Recompensas
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_rewards_granted` | El usuario asignó minutos de recompensa al dispositivo. | `amount` (cantidad de minutos) | Gamificación / recompensas de uso. |
|
||||
|
||||
### Podómetro (Activity Meter)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_activity_pedometer_toggled` | El usuario activó/desactivó el contador de pasos. | `enabled` (`true` / `false`) | |
|
||||
| `legacy_device_activity_meter_time_range_changed` | El usuario cambió el rango de fechas en la pantalla de pasos. | `range` (`today`, `seven_days`, `thirty_days`, `custom`) | **Engagement profundo:** el padre no solo abre la pantalla, sino que investiga distintos períodos. |
|
||||
|
||||
### Salud (ritmo cardíaco / SpO2)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_health_heart_rate_frequency_changed` | El usuario cambió la frecuencia de medición del ritmo cardíaco. | `frequency_seconds` | Personalización de monitoreo de salud. |
|
||||
| `legacy_device_health_measurement_started` | El usuario inició una medición manual de ritmo cardíaco. | — | Interés en datos de salud del niño. |
|
||||
| `legacy_device_health_time_range_changed` | El usuario cambió el rango de fechas en la pantalla de salud. | `range` | Engagement profundo: padres revisando el historial de salud. |
|
||||
|
||||
### Uso de aplicaciones (Apps Use)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_apps_use_time_range_changed` | El usuario cambió el rango de fechas en la pantalla de uso de apps. | `range`, `total_duration_seconds` (total acumulado del período), `top_app_name` (app más usada en ese período) | **El evento más rico del módulo.** Permite a marketing segmentar directo: "padres cuyos hijos usan más TikTok que YouTube", "familias con uso > 4hs/día", etc. |
|
||||
|
||||
### Historial de llamadas
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_device_call_history_filter_changed` | El usuario cambió el filtro del historial. | `filter` (`all`, `incoming`, `outgoing`, `missed`) | **Cuando se filtra `missed` es señal de preocupación** del padre: busca llamadas perdidas del hijo. |
|
||||
|
||||
---
|
||||
|
||||
## Contactos del dispositivo
|
||||
|
||||
Prefijo `legacy_contacts_*` — contactos permitidos para llamadas desde el
|
||||
dispositivo del niño.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_contacts_added` | El usuario agregó un contacto al dispositivo. | `total_count` (cantidad total de contactos DESPUÉS del add) | Configuración inicial o expansión de la agenda. El `total_count` permite segmentar "padres con agenda chica vs grande". |
|
||||
| `legacy_contacts_edited` | El usuario editó un contacto existente. | — | |
|
||||
| `legacy_contacts_deleted` | El usuario eliminó un contacto del dispositivo. | `total_count` (cantidad total DESPUÉS del delete) | |
|
||||
|
||||
---
|
||||
|
||||
## Ajustes
|
||||
|
||||
Prefijo `legacy_settings_*` — configuración general del dispositivo
|
||||
(alarmas, SOS, bloqueos, idioma, red, etc).
|
||||
|
||||
### Alarmas
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_alarm_added` | Alarma nueva creada. | `time` (`HH:mm`) | Uso del feature de alarma. El `time` permite ver qué horarios son más populares (despertador matutino, hora del colegio, etc). |
|
||||
| `legacy_settings_alarm_updated` | Alarma existente editada. | `time` (el NUEVO `HH:mm`) | Refinamiento. |
|
||||
| `legacy_settings_alarm_removed` | Alarma eliminada. | — | |
|
||||
|
||||
### Contactos SOS
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_sos_contact_added` | Contacto SOS agregado. | `total_count` | Configuración de seguridad. Muy importante. |
|
||||
| `legacy_settings_sos_contact_removed` | Contacto SOS removido. | `total_count` | |
|
||||
|
||||
### Whitelist del teléfono (bloqueo de llamadas)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_block_phone_contact_added` | Contacto agregado a la whitelist de llamadas permitidas. | `total_count` | Control parental. |
|
||||
| `legacy_settings_block_phone_contact_removed` | Contacto removido de la whitelist. | `total_count` | |
|
||||
|
||||
### Control parental (funciones desactivadas)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_disable_functions_changed` | El usuario guardó cambios en la pantalla de funciones desactivadas. | — | Engagement con control parental (evento agregado). |
|
||||
| `legacy_settings_disable_functions_keyboard_toggled` | Se guardó con el teclado habilitado/deshabilitado. | `enabled` | Control granular. |
|
||||
| `legacy_settings_disable_functions_gps_toggled` | Se guardó con el GPS habilitado/deshabilitado. | `enabled` | Control granular. |
|
||||
|
||||
### Otros ajustes del dispositivo
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_language_changed` | Se cambió el idioma del dispositivo. | `language` (ej. `es`, `en`) | |
|
||||
| `legacy_settings_alerts_configured` | El usuario guardó cambios en las alertas. | `alert_count` (cuántas alertas activas), `alerts_enabled` (lista separada por comas truncada a 100 chars) | Permite ver qué alertas son más populares y cuántas alertas promedio configuran los padres. |
|
||||
| `legacy_settings_timezone_changed` | Se cambió la zona horaria. | `timezone` | |
|
||||
| `legacy_settings_wifi_added` | Se agregó una red WiFi permitida. | `total_count` | |
|
||||
| `legacy_settings_wifi_removed` | Se eliminó una red WiFi permitida. | `total_count` | |
|
||||
| `legacy_settings_sound_changed` | Se cambió el modo de sonido del dispositivo. | `mode` (`normal` / `silent` / `vibrate`) | Preferencia de perfil sonoro del niño. |
|
||||
| `legacy_settings_sync_clock_triggered` | El usuario disparó una sincronización manual del reloj del dispositivo. | — | |
|
||||
| `legacy_settings_battery_night_mode_toggled` | El usuario activó/desactivó el modo nocturno (ahorro de batería). | `enabled` | |
|
||||
|
||||
### Gestión remota del dispositivo (comandos destructivos)
|
||||
|
||||
Estos eventos son **muy importantes** para churn analysis. Un
|
||||
`factory_reset` típicamente precede a un desvinculado y potencial churn.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_settings_remote_management_shutdown` | El usuario apagó el dispositivo remotamente. | — | Acción poco frecuente. |
|
||||
| `legacy_settings_remote_management_restart` | El usuario reinició el dispositivo remotamente. | — | Típicamente usado cuando hay problemas técnicos. |
|
||||
| `legacy_settings_remote_management_factory_reset` | **CHURN SIGNAL** — El usuario reseteó el dispositivo a fábrica. | — | Borra el dispositivo. Frecuentemente precede un `legacy_account_linked_device_unlinked` y luego `legacy_account_deletion_*`. Cruzar para medir correlación. |
|
||||
|
||||
---
|
||||
|
||||
## Soporte
|
||||
|
||||
Prefijo `legacy_support_*` — solo 1 evento hoy, medirá la demanda de
|
||||
soporte.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_support_contact_initiated` | El usuario tocó el botón para contactar soporte (ej. abrir el cliente de email). | `channel` (`email` hoy; en el futuro también `phone`, `whatsapp`), `country` (país seleccionado en el formulario) | Demanda de soporte **por país**: permite ver dónde se originan más tickets. Nota: mide la **intención** de contactar, no confirma envío. |
|
||||
|
||||
---
|
||||
|
||||
## Onboarding
|
||||
|
||||
Prefijo `legacy_onboarding_*` — los slides de intro iniciales de la app.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_onboarding_step_changed` | El usuario pasó a un nuevo slide del intro. | `step_index` (número de slide, empieza en 0) | Medir cuántos slides el usuario ve antes de empezar. |
|
||||
|
||||
---
|
||||
|
||||
## Panel principal
|
||||
|
||||
Prefijo `legacy_control_panel_*` — acciones en el home del legacy.
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_control_panel_device_selected` | El usuario cambió el dispositivo activo (útil cuando hay varios hijos). | `total_devices` (cuántos dispositivos tiene vinculados) | Qué dispositivo está monitoreando activamente. El `total_devices` permite **segmentar por tamaño de familia** (1 hijo, 2 hijos, 3+). |
|
||||
| `legacy_control_panel_positions_refreshed` | El usuario tiró del pull-to-refresh o tocó "actualizar" en el dashboard. | — | Preocupación activa del usuario. Indicador de engagement alto. |
|
||||
|
||||
---
|
||||
|
||||
## Ubicación y mapa
|
||||
|
||||
Prefijo `legacy_location_*` — **el feature más rico del producto**. Acá
|
||||
capturamos toda la interacción del usuario con el mapa: ver el trayecto,
|
||||
crear zonas seguras, ver lugares frecuentes, cambiar frecuencia de
|
||||
actualización, etc.
|
||||
|
||||
### Geofences (zonas seguras) — CRUD básico
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_geofence_created` | Se creó una geofence (API confirmó). | — | **Conversión final** del funnel de creación. |
|
||||
| `legacy_location_geofence_updated` | Se actualizó una geofence existente. | — | Refinamiento de configuración de zonas. |
|
||||
| `legacy_location_geofence_deleted` | Se eliminó una geofence. | — | |
|
||||
|
||||
### Lugares frecuentes — CRUD básico
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_frequent_place_created` | Se creó un lugar frecuente (API confirmó). | — | **Conversión final** del funnel. |
|
||||
| `legacy_location_frequent_place_updated` | Se actualizó un lugar frecuente. | — | |
|
||||
| `legacy_location_frequent_place_deleted` | Se eliminó un lugar frecuente. | — | |
|
||||
|
||||
### Funnel de creación de lugares (geofences y frequent places)
|
||||
|
||||
Este es el funnel más valioso del módulo de ubicación. Permite medir
|
||||
**cuánta gente empieza a crear una zona vs. cuánta termina**.
|
||||
|
||||
```
|
||||
legacy_location_place_creation_started (top: 100 %)
|
||||
|
||||
legacy_location_point_confirmed (paso 1 completado)
|
||||
|
||||
legacy_location_radius_confirmed (solo geofences — paso 2)
|
||||
|
||||
legacy_location_geofence_created (bottom: API OK)
|
||||
o legacy_location_frequent_place_created
|
||||
```
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_place_creation_started` | El usuario tocó "agregar zona" o "agregar lugar frecuente". | `mode` (`geofence` / `frequent_place`) | Top del funnel. |
|
||||
| `legacy_location_point_confirmed` | El usuario tocó el mapa para fijar el centro del lugar. | `mode` | Paso 1 del funnel completado. |
|
||||
| `legacy_location_radius_confirmed` | El usuario confirmó el radio de la geofence (solo aplica a geofences). | `radius` (metros), `is_editing` (`true` si estaba editando una existente, `false` si es nueva) | Paso 2 del funnel completado. Permite también **analizar qué tamaños de zonas eligen los usuarios** (radios más comunes casa, escuela, etc.). |
|
||||
| `legacy_location_place_creation_cancelled` | El usuario salió del flujo de creación/edición antes de terminar. | `mode`, `at_step` (`picking_point` o `adjusting_radius`) | **Drop-off del funnel**. El `at_step` dice exactamente dónde lo perdimos. |
|
||||
|
||||
### Exploración y edición
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_geofence_selected` | El usuario tocó una geofence del mapa para verla. | — | Engagement: el usuario está mirando sus zonas. |
|
||||
| `legacy_location_geofence_dismissed` | El usuario cerró el popup de la geofence sin hacer nada. | — | "Miró pero no editó". Indicador de exploración. |
|
||||
| `legacy_location_geofence_edit_started` | El usuario tocó "editar" en una geofence seleccionada. | — | Intención de editar. Mid-funnel de edición. |
|
||||
| `legacy_location_frequent_place_selected` | El usuario tocó un lugar frecuente para verlo. | — | Engagement. |
|
||||
| `legacy_location_frequent_place_dismissed` | El usuario cerró el popup del lugar frecuente. | — | |
|
||||
| `legacy_location_history_position_selected` | El usuario tocó un punto del historial de ubicaciones en el mapa. | — | Inspección detallada del trayecto. |
|
||||
| `legacy_location_history_position_dismissed` | El usuario cerró el detalle del punto de historial. | — | |
|
||||
|
||||
### Historial de ubicaciones
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_history_loaded` | El usuario cargó el historial para un rango de fechas. | — | Interés en el historial. |
|
||||
| `legacy_location_history_cleared` | El usuario limpió el trayecto del mapa. | — | |
|
||||
|
||||
### Frecuencia de ubicación (privacidad vs. precisión)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_frequency_updated` | El usuario cambió cada cuánto el dispositivo manda su posición. | `frequency_seconds` (ej. `60`, `300`, `900`) | **Dato súper útil:** indica preferencia entre privacidad y precisión/batería. Correlacionar con retention. |
|
||||
|
||||
### Capas del mapa (toggles de visibilidad)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_map_geofences_toggled` | El usuario mostró/ocultó las geofences en el mapa. | `visible` (`true` / `false`) | |
|
||||
| `legacy_location_map_frequent_places_toggled` | El usuario mostró/ocultó los lugares frecuentes. | `visible` | |
|
||||
| `legacy_location_map_route_trail_toggled` | El usuario mostró/ocultó la línea del trayecto histórico. | `visible` | |
|
||||
|
||||
### Modo "seguir en vivo"
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_following_toggled` | El usuario activó/desactivó el modo "seguir dispositivo" (el mapa se re-centra automáticamente). | `enabled` (`true` / `false`) | **Engagement alto:** el usuario está viendo al hijo en tiempo real. Correlacionar con horarios (ej. entrada/salida del cole). |
|
||||
|
||||
### UI del mapa (chrome)
|
||||
|
||||
| Evento | Cuándo se dispara | Parámetros | Qué significa para marketing |
|
||||
|---|---|---|---|
|
||||
| `legacy_location_map_actions_expanded` | El usuario abrió/cerró el drawer de acciones del mapa. | `expanded` (`true` / `false`) | Indica conocimiento de la UI. |
|
||||
| `legacy_location_map_zoomed` | El usuario hizo zoom in/out y se quedó en ese nivel (con debounce de 1 segundo para no spamear). | `zoom` (nivel de zoom final) | Nivel de detalle con el que los usuarios miran el mapa. Un zoom alto indica "me importa ver dónde exactamente está". |
|
||||
| `legacy_location_map_style_changed` | El usuario eligió otro estilo visual para el mapa desde el selector de capas. | `style` (`standard` / `voyager` / `light` / `dark` / `satellite`) | Personalización de la experiencia. **Satellite** es el más usado por padres que quieren ver edificios reales. |
|
||||
| `legacy_location_map_center_tapped` | El usuario tocó el botón "centrar en el dispositivo" del mapa. | — | Acción de re-centrado manual. Indica que el mapa se desplazó y el usuario quiere volver al hijo. |
|
||||
| `legacy_location_map_refresh_tapped` | El usuario tocó el botón de refresco dentro del mapa (distinto del pull-to-refresh del control panel). | — | **Engagement intenso:** el usuario quiere la posición más reciente AHORA. Suele dispararse en momentos de ansiedad. |
|
||||
| `legacy_location_shared` | El usuario tocó "compartir ubicación" — abre el share sheet nativo para mandar la posición del hijo a otra app. | — | **Acción viral del producto.** Es la más importante para crecimiento orgánico: indica que el usuario está mandando data del producto a contactos fuera de la app (familia, pareja, abuelos). |
|
||||
| `legacy_location_list_sheet_opened` | El usuario abrió el bottom sheet con la lista de geofences, lugares frecuentes e historial. | — | Quiere explorar todo lo que tiene configurado. Mid-funnel de gestión. |
|
||||
| `legacy_location_history_type_filter_changed` | El usuario filtró el historial por tipo de posición. | `type` (`gps` / `wifi` / `sos` / `all` cuando limpia el filtro) | Indica interés en una fuente de datos específica. **`sos`** filtrado es señal de un evento crítico que el usuario está investigando. |
|
||||
|
||||
---
|
||||
|
||||
## Cómo usar este catálogo
|
||||
|
||||
### Para construir funnels
|
||||
Tomá un evento "inicio" y uno "fin" en Firebase Analytics Engagement
|
||||
**Funnels** y comparalos:
|
||||
- **Signup:** `legacy_auth_signup_started _completed`
|
||||
- **Login:** `legacy_auth_login_attempt _2fa_verified`
|
||||
- **Activación (aha moment):** `legacy_device_setup_started _completed`
|
||||
- **Creación de zona segura:** `legacy_location_place_creation_started _geofence_created`
|
||||
- **Churn:** `legacy_account_deletion_initiated _deletion_completed`
|
||||
|
||||
### Para segmentar audiencias
|
||||
En **Audiences** podés filtrar por user properties (`user_language`,
|
||||
`user_has_phone`, etc.) y cruzarlo con cualquiera de estos eventos.
|
||||
|
||||
### Para detectar problemas
|
||||
Filtrar por los eventos con `_failed` o `_failure` y mirar los `reason`
|
||||
más frecuentes en la pestaña Events Parameter.
|
||||
|
||||
### Para medir engagement diario
|
||||
Los eventos `legacy_control_panel_positions_refreshed`,
|
||||
`legacy_location_following_toggled` y las screen_views del mapa son los
|
||||
indicadores más fuertes de usuarios activos y preocupados.
|
||||
|
||||
---
|
||||
|
||||
## Eventos propuestos para el futuro (NO implementados aún)
|
||||
|
||||
Esta sección es la **wishlist** para cuando existan los features o lleguen
|
||||
las decisiones pendientes.
|
||||
|
||||
### Cuando exista el plan premium/suscripción
|
||||
- `purchase` / `purchase_subscription` (con `value`, `currency`, `transaction_id`)
|
||||
- `action_click_gopremium` (botón de upgrade)
|
||||
- `subscription_error_payment` / `subscription_canceled_payment`
|
||||
- User property `user_plan` (`free` / `premium` / `family`)
|
||||
|
||||
### Limit popups / free-tier walls
|
||||
- `legacy_limit_hit` con `limit_type` (max_devices, max_contacts, etc.)
|
||||
- `legacy_limit_popup_shown`
|
||||
- `legacy_limit_popup_upgrade_clicked`
|
||||
|
||||
### Referral / invitación
|
||||
- `legacy_referral_screen_viewed`
|
||||
- `legacy_referral_code_shared` (con `channel`)
|
||||
- `legacy_referral_signup_completed`
|
||||
|
||||
### NPS / rating
|
||||
- `legacy_nps_prompt_shown`
|
||||
- `legacy_nps_score_submitted` (con `score` 0–10)
|
||||
- `legacy_app_rating_submitted`
|
||||
|
||||
### Push notification engagement
|
||||
- `legacy_notification_received` (background)
|
||||
- `legacy_notification_opened` (tap app abre)
|
||||
- `legacy_notification_dismissed`
|
||||
|
||||
### Aha moments
|
||||
- `legacy_first_device_connected` (primera vez que el usuario vincula un dispositivo — requiere persistencia de "primera vez")
|
||||
- `legacy_first_session_completed`
|
||||
|
||||
### A/B testing
|
||||
- `ab_test_<experiment_name>` (cuando empecemos experimentos con Remote Config)
|
||||
|
||||
### Errores de API / health técnica
|
||||
- `legacy_api_error` con `endpoint`, `status_code` (detectar endpoints flakey)
|
||||
- `legacy_session_expired`
|
||||
|
||||
---
|
||||
|
||||
## Referencias técnicas
|
||||
|
||||
- **Proyecto Firebase:** `sf-platform-pre` (para dev+staging) / `sf-platform-prod` (pendiente de crear)
|
||||
- **Package Dart:** `packages/sf_tracking/`
|
||||
- **Mixins:** Cada grupo de eventos vive en un mixin aparte dentro del package (`auth_tracking.dart`, `location_tracking.dart`, etc).
|
||||
- **GDPR:** Cada evento incluye automáticamente el parámetro `consent_status` para permitir filtrado post-hoc en BigQuery cuando se implemente el consent screen.
|
||||
- **Ambiente:** `env` se setea como user property (`development` / `staging` / `production`), por lo que **todos los reportes pueden filtrarse por ambiente** y producción no se va a mezclar con testing.
|
||||
|
||||
---
|
||||
|
||||
## Changelog del catálogo
|
||||
|
||||
- **2026-04-07** — Creación inicial. 61 eventos del módulo legacy implementados y validados en device físico (iPhone 14 Pro iOS 18 + Samsung Galaxy A55 Android 15).
|
||||
- **2026-04-07** — Se agregaron 16 eventos nuevos al módulo de ubicación (funnel de creación, exploración, edición, follow mode, map zoom debounced, history).
|
||||
- **2026-04-07** — Se expandió el tracking de `device_management` con 8 eventos nuevos y 3 enriquecimientos de parámetros:
|
||||
- NUEVOS: `legacy_device_locate_success/failure`, `legacy_device_remote_connection_picture_viewed`, `legacy_device_activity_meter_time_range_changed`, `legacy_device_health_time_range_changed`, `legacy_device_apps_use_time_range_changed` (con total_duration_seconds y top_app_name), `legacy_device_call_history_filter_changed`.
|
||||
- ENRIQUECIDOS: `legacy_device_volume_control_changed` ahora dispara un evento por cada tipo (media/ringtone/alarm) que efectivamente cambió; `legacy_device_scheduled_activity_added/updated` ahora incluyen `week_day` y `period`; `legacy_contacts_added/deleted` ahora incluyen `total_count`.
|
||||
- **2026-04-07** — Se expandió el tracking de `settings` con 3 eventos nuevos y 7 enriquecimientos de parámetros:
|
||||
- NUEVOS: `legacy_settings_remote_management_shutdown/restart/factory_reset` (churn signal crítico).
|
||||
- ENRIQUECIDOS: `legacy_settings_alarm_added/updated` ahora incluyen `time`; `legacy_settings_sos_contact_added/removed` incluyen `total_count`; `legacy_settings_block_phone_contact_added/removed` incluyen `total_count`; `legacy_settings_wifi_added/removed` incluyen `total_count`; `legacy_settings_sound_changed` incluye `mode`; `legacy_settings_alerts_configured` incluye `alert_count` y `alerts_enabled`.
|
||||
- **2026-04-07** — Se expandió el tracking de `device_setup` con 3 eventos nuevos y 2 enriquecimientos críticos:
|
||||
- NUEVOS: `legacy_device_setup_qr_scanned`, `legacy_device_setup_manual_code_entered`, `legacy_device_setup_reset_for_new_kid` (señal de familias con múltiples hijos).
|
||||
- ENRIQUECIDOS: `legacy_device_setup_step_completed` ahora incluye `duration_seconds` (tiempo por paso — fricción directa); `legacy_device_setup_completed` ahora incluye `child_gender`, `relation_type`, `child_age_years` ( demográficos del usuario final para personas de marketing).
|
||||
- **2026-04-07** — Se expandió el tracking de `legacy_auth` con 3 eventos nuevos para el funnel interno del signup:
|
||||
- NUEVOS: `legacy_auth_signup_step_completed`, `legacy_auth_signup_step_back`, `legacy_auth_signup_step_validation_failed` (originalmente con `step_index` 0-2; reducido a 0-1 en abril 2026 al simplificar el signup).
|
||||
- **2026-04-07** — Pasada final de cobertura en `legacy_auth`, `account`, `support`, `control_panel`: 6 eventos nuevos y 2 enriquecimientos.
|
||||
- NUEVOS AUTH: `legacy_auth_password_reset_completed`, `legacy_auth_password_reset_failed` (con `reason` granular), `legacy_auth_link_phone_code_request_failed`, `legacy_auth_link_phone_code_verification_failed`.
|
||||
- NUEVOS ACCOUNT: `legacy_account_linked_device_renamed`, `legacy_account_app_user_delete_triggered`.
|
||||
- ENRIQUECIDOS: `legacy_support_contact_initiated` ahora incluye `country` además de `channel`; `legacy_control_panel_device_selected` ahora incluye `total_devices` (proxy de tamaño de familia).
|
||||
- **2026-04-07** — Se expandió la cobertura de los widgets del módulo `location` con 6 eventos nuevos sobre acciones top-level del mapa:
|
||||
- NUEVOS: `legacy_location_map_style_changed` (selector de capas), `legacy_location_map_center_tapped`, `legacy_location_map_refresh_tapped`, `legacy_location_shared` ( acción viral del producto), `legacy_location_list_sheet_opened`, `legacy_location_history_type_filter_changed` (con `type` para detectar interés en posiciones SOS).
|
||||
- **2026-04-15** — Cambios de producto en `legacy_auth`:
|
||||
- **Signup reducido a 2 pasos** (antes 3). Se quitaron los campos de documento, fecha de nacimiento, lugar de nacimiento, país de nacimiento, relación con el niño y dirección completa. El request al backend ahora solo incluye `firstName`, `lastName`, `email`, `phone` (E.164), `language` (del locale del dispositivo) y `password`. `step_index` de los eventos `legacy_auth_signup_step_*` pasa de 0-2 a 0-1.
|
||||
- **Recover password solo por email**: se eliminó la UI de teléfono móvil en ambos screens del flujo (`request_recovery` y `new_password`). Los eventos del flujo se mantienen igual pero ahora siempre corresponden al canal email. Se eliminó del state `recoveryFormat` (ya no hay rama SMS).
|
||||
- **User properties (Firebase Analytics)** ahora se sincronizan solo en shells autenticados (dashboards legacy y payment), no en rutas públicas. Los eventos en sí no cambian — solo se movió el disparador de la sync para evitar llamadas espurias a `/auth/me` en login/signup/recover_password.
|
||||
353
apps/mobile_app/docs/analytics-catalog.md
Normal file
353
apps/mobile_app/docs/analytics-catalog.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# Catálogo de Eventos — SaveFamily
|
||||
|
||||
> Documento para el equipo de Marketing. Lista todos los eventos que la app registra y describe el momento exacto en que se dispara cada uno.
|
||||
|
||||
---
|
||||
|
||||
## Índice
|
||||
|
||||
1. [Pantallas de la app](#pantallas-de-la-app)
|
||||
2. [Autenticación](#autenticación)
|
||||
3. [Cuenta](#cuenta)
|
||||
4. [Alta de dispositivo (reloj/wearable del niño)](#alta-de-dispositivo)
|
||||
5. [Funciones del dispositivo](#funciones-del-dispositivo)
|
||||
6. [Contactos del dispositivo](#contactos-del-dispositivo)
|
||||
7. [Ajustes del dispositivo](#ajustes-del-dispositivo)
|
||||
8. [Soporte](#soporte)
|
||||
9. [Onboarding](#onboarding)
|
||||
10. [Panel principal (home)](#panel-principal)
|
||||
11. [Ubicación y mapa](#ubicación-y-mapa)
|
||||
|
||||
---
|
||||
|
||||
## Pantallas de la app
|
||||
|
||||
Cada vez que el usuario navega a una pantalla, queda registrada
|
||||
automáticamente. También se registran los cambios entre pestañas del menú
|
||||
inferior.
|
||||
|
||||
Pantallas registradas:
|
||||
|
||||
- Pantalla de carga inicial
|
||||
- Onboarding / intro
|
||||
- Login
|
||||
- Alta de cuenta (signup)
|
||||
- Recuperación de contraseña
|
||||
- Wizard de alta de reloj/dispositivo
|
||||
- Inicio de vinculación de teléfono
|
||||
- Verificación del código de vinculación
|
||||
- Dashboard principal (home)
|
||||
- Soporte / atención al cliente
|
||||
- Menú de cuenta
|
||||
- Editar datos personales
|
||||
- Cambiar contraseña
|
||||
- Dispositivos vinculados a la cuenta
|
||||
- Sub-usuarios de la app
|
||||
- Eliminación de cuenta
|
||||
- Menú de gestión del dispositivo
|
||||
- Actividades programadas
|
||||
- Contactos
|
||||
- Editar contacto
|
||||
- Salud (ritmo cardíaco, oxígeno en sangre)
|
||||
- Conexión remota (cámara y llamada)
|
||||
- Localizar dispositivo
|
||||
- Recompensas
|
||||
- Medidor de actividad / pasos
|
||||
- Uso de aplicaciones
|
||||
- Control de volumen
|
||||
- Historial de llamadas
|
||||
- Imagen de fondo del dispositivo
|
||||
- Mapa de ubicación
|
||||
- Chat
|
||||
- Menú de ajustes
|
||||
- Alarmas
|
||||
- Gestión remota
|
||||
- Contactos SOS
|
||||
- Sonido del dispositivo
|
||||
- Sincronización del reloj
|
||||
- Gestión de apps instaladas
|
||||
- Batería / modo nocturno
|
||||
- Bloqueo de teléfono (whitelist)
|
||||
- Desactivar funciones (teclado, GPS)
|
||||
- Idioma del dispositivo
|
||||
- Notificaciones del dispositivo
|
||||
- Encendido/apagado remoto
|
||||
- Alertas
|
||||
- Zona horaria
|
||||
- Configuración WiFi
|
||||
|
||||
---
|
||||
|
||||
## Autenticación
|
||||
|
||||
### Login
|
||||
|
||||
- **legacy_auth_login_attempt** — El usuario pulsa "Iniciar sesión" después de completar el formulario.
|
||||
- **legacy_auth_login_success** — Email y contraseña aceptados (todavía falta el segundo factor).
|
||||
- **legacy_auth_login_failure** — El intento de login fue rechazado.
|
||||
|
||||
### Doble factor (2FA)
|
||||
|
||||
- **legacy_auth_2fa_requested** — Se le envió el código de verificación al usuario.
|
||||
- **legacy_auth_2fa_verified** — El código fue aceptado y la sesión está activa (login completado).
|
||||
- **legacy_auth_2fa_failure** — El código fue rechazado (incorrecto o expirado).
|
||||
- **legacy_auth_2fa_resend** — El usuario pidió que le reenvíen el código.
|
||||
|
||||
### Alta de cuenta (signup)
|
||||
|
||||
El alta es un wizard de 2 pasos (datos personales → contraseña).
|
||||
|
||||
- **legacy_auth_signup_started** — El usuario envió el formulario final del alta.
|
||||
- **legacy_auth_signup_completed** — La cuenta se creó exitosamente.
|
||||
- **legacy_auth_signup_failed** — El alta falló.
|
||||
- **legacy_auth_signup_step_completed** — El usuario completó un paso del wizard y avanzó al siguiente.
|
||||
- **legacy_auth_signup_step_back** — El usuario volvió al paso anterior dentro del wizard.
|
||||
- **legacy_auth_signup_step_validation_failed** — El usuario intentó avanzar pero el formulario tenía errores.
|
||||
|
||||
### Recuperación de contraseña
|
||||
|
||||
Flujo exclusivo por email (no hay opción de SMS).
|
||||
|
||||
- **legacy_auth_password_reset_requested** — El usuario inició el flujo de "olvidé mi contraseña" tipeando su email.
|
||||
- **legacy_auth_password_reset_email_sent** — Se envió el email con el enlace de recuperación.
|
||||
- **legacy_auth_password_reset_completed** — El usuario guardó exitosamente su nueva contraseña.
|
||||
- **legacy_auth_password_reset_failed** — El intento de guardar la nueva contraseña falló.
|
||||
|
||||
### Vinculación de teléfono
|
||||
|
||||
- **legacy_auth_link_phone_code_requested** — El usuario envió su número y pidió el código.
|
||||
- **legacy_auth_link_phone_code_request_failed** — Falló el envío del código.
|
||||
- **legacy_auth_link_phone_code_verified** — El código fue verificado, número vinculado.
|
||||
- **legacy_auth_link_phone_code_verification_failed** — El código no fue aceptado.
|
||||
|
||||
### Cierre de sesión
|
||||
|
||||
- **legacy_auth_logout** — El usuario cerró sesión.
|
||||
|
||||
---
|
||||
|
||||
## Cuenta
|
||||
|
||||
- **legacy_account_personal_data_edited** — El usuario guardó cambios en sus datos personales (nombre, apellido, teléfono).
|
||||
- **legacy_account_password_changed** — El usuario cambió su contraseña exitosamente.
|
||||
- **legacy_account_password_change_failed** — El cambio de contraseña falló.
|
||||
- **legacy_account_linked_device_unlinked** — El usuario quitó un dispositivo vinculado de su cuenta.
|
||||
- **legacy_account_linked_device_renamed** — El usuario renombró un dispositivo vinculado.
|
||||
- **legacy_account_app_user_delete_triggered** — El usuario tocó "eliminar" en la pantalla de sub-usuarios.
|
||||
- **legacy_account_deletion_initiated** — El usuario entró al flujo de "eliminar cuenta". Señal temprana de churn.
|
||||
- **legacy_account_deletion_confirmed** — El usuario confirmó la eliminación.
|
||||
- **legacy_account_deletion_completed** — La cuenta se eliminó.
|
||||
- **legacy_account_deletion_cancelled** — El usuario canceló antes de confirmar la eliminación.
|
||||
|
||||
---
|
||||
|
||||
## Alta de dispositivo
|
||||
|
||||
Vincular el reloj/dispositivo del niño a la cuenta del adulto.
|
||||
|
||||
### Wizard de alta
|
||||
|
||||
- **legacy_device_setup_started** — El usuario entró al wizard de alta de dispositivo.
|
||||
- **legacy_device_setup_step_completed** — El usuario completó un paso del wizard. Se registra cuánto tiempo tardó en ese paso.
|
||||
- **legacy_device_setup_completed** — El dispositivo quedó vinculado. Se registra género y edad del niño y la relación con el adulto.
|
||||
- **legacy_device_setup_failed** — Falló el alta del dispositivo.
|
||||
- **legacy_device_setup_cancelled** — El usuario abandonó el wizard.
|
||||
|
||||
### Cómo se introdujo el código del reloj
|
||||
|
||||
- **legacy_device_setup_qr_scanned** — El usuario escaneó el código QR del reloj.
|
||||
- **legacy_device_setup_manual_code_entered** — El usuario tipeó el código manualmente.
|
||||
|
||||
### Familias con varios hijos
|
||||
|
||||
- **legacy_device_setup_reset_for_new_kid** — Después de terminar un alta, el usuario eligió "agregar otro hijo".
|
||||
|
||||
---
|
||||
|
||||
## Funciones del dispositivo
|
||||
|
||||
### Localizar dispositivo
|
||||
|
||||
- **legacy_device_locate_requested** — El usuario pulsó el botón de localizar.
|
||||
- **legacy_device_locate_success** — La orden de localizar se envió al dispositivo.
|
||||
- **legacy_device_locate_failure** — La orden de localizar falló.
|
||||
|
||||
### Conexión remota (cámara y llamada)
|
||||
|
||||
- **legacy_device_remote_connection_started** — El usuario abrió la pantalla de conexión remota.
|
||||
- **legacy_device_remote_connection_photo_taken** — El usuario pidió una foto desde la cámara remota.
|
||||
- **legacy_device_remote_connection_call_initiated** — El usuario inició una llamada con el dispositivo.
|
||||
- **legacy_device_remote_connection_picture_viewed** — El usuario navegó entre las fotos tomadas remotamente.
|
||||
|
||||
### Volumen del dispositivo
|
||||
|
||||
- **legacy_device_volume_control_changed** — El usuario guardó un cambio de volumen. Se dispara una vez por cada tipo modificado (multimedia, tono de llamada, alarma).
|
||||
|
||||
### Imagen de fondo del reloj
|
||||
|
||||
- **legacy_device_background_image_changed** — El usuario eligió una imagen existente como fondo.
|
||||
- **legacy_device_background_image_uploaded** — El usuario subió una foto personal como fondo.
|
||||
|
||||
### Actividades programadas (rutinas en el dispositivo)
|
||||
|
||||
- **legacy_device_scheduled_activity_added** — El usuario creó una nueva actividad programada. Se registra el día de la semana y el horario.
|
||||
- **legacy_device_scheduled_activity_updated** — El usuario editó una actividad programada.
|
||||
- **legacy_device_scheduled_activity_removed** — El usuario eliminó una actividad programada.
|
||||
|
||||
### Recompensas
|
||||
|
||||
- **legacy_device_rewards_granted** — El usuario otorgó minutos de recompensa al dispositivo. Se registra la cantidad de minutos.
|
||||
|
||||
### Podómetro / pasos
|
||||
|
||||
- **legacy_device_activity_pedometer_toggled** — El usuario activó o desactivó el contador de pasos.
|
||||
- **legacy_device_activity_meter_time_range_changed** — El usuario cambió el rango de fechas en la pantalla de pasos (hoy, 7 días, 30 días, personalizado).
|
||||
|
||||
### Salud (ritmo cardíaco / oxígeno en sangre)
|
||||
|
||||
- **legacy_device_health_heart_rate_frequency_changed** — El usuario cambió la frecuencia con la que se mide el ritmo cardíaco.
|
||||
- **legacy_device_health_measurement_started** — El usuario inició una medición manual.
|
||||
- **legacy_device_health_time_range_changed** — El usuario cambió el rango de fechas en la pantalla de salud.
|
||||
|
||||
### Uso de aplicaciones del dispositivo
|
||||
|
||||
- **legacy_device_apps_use_time_range_changed** — El usuario cambió el rango de fechas en la pantalla de uso de apps. Se registra el tiempo total acumulado y la app más usada del período.
|
||||
|
||||
### Historial de llamadas
|
||||
|
||||
- **legacy_device_call_history_filter_changed** — El usuario filtró el historial (todas, entrantes, salientes, perdidas). Filtrar perdidas suele ser señal de preocupación del adulto.
|
||||
|
||||
---
|
||||
|
||||
## Contactos del dispositivo
|
||||
|
||||
Contactos permitidos para llamar al/desde el dispositivo del niño.
|
||||
|
||||
- **legacy_contacts_added** — El usuario agregó un contacto. Se registra cuántos contactos tiene en total.
|
||||
- **legacy_contacts_edited** — El usuario editó un contacto existente.
|
||||
- **legacy_contacts_deleted** — El usuario eliminó un contacto. Se registra el total restante.
|
||||
|
||||
---
|
||||
|
||||
## Ajustes del dispositivo
|
||||
|
||||
### Alarmas
|
||||
|
||||
- **legacy_settings_alarm_added** — El usuario creó una alarma. Se registra la hora.
|
||||
- **legacy_settings_alarm_updated** — El usuario editó una alarma.
|
||||
- **legacy_settings_alarm_removed** — El usuario eliminó una alarma.
|
||||
|
||||
### Contactos SOS
|
||||
|
||||
- **legacy_settings_sos_contact_added** — El usuario agregó un contacto SOS.
|
||||
- **legacy_settings_sos_contact_removed** — El usuario eliminó un contacto SOS.
|
||||
|
||||
### Whitelist de llamadas (bloqueo de teléfono)
|
||||
|
||||
- **legacy_settings_block_phone_contact_added** — El usuario agregó un contacto a la lista de llamadas permitidas.
|
||||
- **legacy_settings_block_phone_contact_removed** — El usuario quitó un contacto de la lista de llamadas permitidas.
|
||||
|
||||
### Control parental (funciones desactivadas)
|
||||
|
||||
- **legacy_settings_disable_functions_changed** — El usuario guardó cambios en la pantalla de funciones desactivadas.
|
||||
- **legacy_settings_disable_functions_keyboard_toggled** — El usuario activó o desactivó el teclado.
|
||||
- **legacy_settings_disable_functions_gps_toggled** — El usuario activó o desactivó el GPS.
|
||||
|
||||
### Otros ajustes
|
||||
|
||||
- **legacy_settings_language_changed** — El usuario cambió el idioma del dispositivo.
|
||||
- **legacy_settings_alerts_configured** — El usuario guardó cambios en las alertas. Se registra cuántas alertas activas tiene y cuáles están encendidas.
|
||||
- **legacy_settings_timezone_changed** — El usuario cambió la zona horaria.
|
||||
- **legacy_settings_wifi_added** — El usuario agregó una red WiFi permitida.
|
||||
- **legacy_settings_wifi_removed** — El usuario eliminó una red WiFi permitida.
|
||||
- **legacy_settings_sound_changed** — El usuario cambió el modo de sonido del dispositivo (normal / silencio / vibración).
|
||||
- **legacy_settings_sync_clock_triggered** — El usuario disparó una sincronización manual del reloj.
|
||||
- **legacy_settings_battery_night_mode_toggled** — El usuario activó o desactivó el modo nocturno (ahorro de batería).
|
||||
|
||||
### Gestión remota del dispositivo
|
||||
|
||||
- **legacy_settings_remote_management_shutdown** — El usuario apagó el dispositivo a distancia.
|
||||
- **legacy_settings_remote_management_restart** — El usuario reinició el dispositivo a distancia.
|
||||
- **legacy_settings_remote_management_factory_reset** — El usuario restauró el dispositivo a fábrica. Suele preceder al desvinculado y al churn.
|
||||
|
||||
---
|
||||
|
||||
## Soporte
|
||||
|
||||
- **legacy_support_contact_initiated** — El usuario tocó el botón para contactar a soporte. Se registra el canal (email) y el país seleccionado en el formulario.
|
||||
|
||||
---
|
||||
|
||||
## Onboarding
|
||||
|
||||
- **legacy_onboarding_step_changed** — El usuario pasó a un nuevo slide del intro inicial.
|
||||
|
||||
---
|
||||
|
||||
## Panel principal
|
||||
|
||||
- **legacy_control_panel_device_selected** — El usuario cambió el dispositivo activo (útil cuando hay varios hijos). Se registra cuántos dispositivos tiene vinculados.
|
||||
- **legacy_control_panel_positions_refreshed** — El usuario refrescó manualmente el dashboard (pull-to-refresh o botón de actualizar).
|
||||
|
||||
---
|
||||
|
||||
## Ubicación y mapa
|
||||
|
||||
### Zonas seguras (geofences)
|
||||
|
||||
- **legacy_location_geofence_created** — Se creó una nueva zona segura.
|
||||
- **legacy_location_geofence_updated** — Se editó una zona segura existente.
|
||||
- **legacy_location_geofence_deleted** — Se eliminó una zona segura.
|
||||
|
||||
### Lugares frecuentes
|
||||
|
||||
- **legacy_location_frequent_place_created** — Se creó un nuevo lugar frecuente.
|
||||
- **legacy_location_frequent_place_updated** — Se editó un lugar frecuente existente.
|
||||
- **legacy_location_frequent_place_deleted** — Se eliminó un lugar frecuente.
|
||||
|
||||
### Funnel de creación de lugares (zonas y frecuentes)
|
||||
|
||||
- **legacy_location_place_creation_started** — El usuario tocó "agregar zona" o "agregar lugar frecuente".
|
||||
- **legacy_location_point_confirmed** — El usuario fijó el centro del lugar tocando el mapa.
|
||||
- **legacy_location_radius_confirmed** — El usuario confirmó el radio de la zona segura. Se registra el tamaño del radio.
|
||||
- **legacy_location_place_creation_cancelled** — El usuario abandonó el flujo de creación o edición. Se registra en qué paso lo dejó.
|
||||
|
||||
### Exploración y edición
|
||||
|
||||
- **legacy_location_geofence_selected** — El usuario tocó una zona segura del mapa para verla.
|
||||
- **legacy_location_geofence_dismissed** — El usuario cerró el detalle de la zona sin hacer cambios.
|
||||
- **legacy_location_geofence_edit_started** — El usuario tocó "editar" en una zona seleccionada.
|
||||
- **legacy_location_frequent_place_selected** — El usuario tocó un lugar frecuente para verlo.
|
||||
- **legacy_location_frequent_place_dismissed** — El usuario cerró el detalle del lugar frecuente.
|
||||
- **legacy_location_history_position_selected** — El usuario tocó un punto del historial de ubicaciones en el mapa.
|
||||
- **legacy_location_history_position_dismissed** — El usuario cerró el detalle del punto del historial.
|
||||
|
||||
### Historial de ubicaciones
|
||||
|
||||
- **legacy_location_history_loaded** — El usuario cargó el historial de ubicaciones para un rango de fechas.
|
||||
- **legacy_location_history_cleared** — El usuario limpió el trayecto del mapa.
|
||||
|
||||
### Frecuencia de ubicación
|
||||
|
||||
- **legacy_location_frequency_updated** — El usuario cambió cada cuánto el dispositivo manda su posición.
|
||||
|
||||
### Capas del mapa
|
||||
|
||||
- **legacy_location_map_geofences_toggled** — El usuario mostró u ocultó las zonas seguras en el mapa.
|
||||
- **legacy_location_map_frequent_places_toggled** — El usuario mostró u ocultó los lugares frecuentes en el mapa.
|
||||
- **legacy_location_map_route_trail_toggled** — El usuario mostró u ocultó la línea del trayecto histórico.
|
||||
|
||||
### Modo "seguir en vivo"
|
||||
|
||||
- **legacy_location_following_toggled** — El usuario activó o desactivó el modo "seguir dispositivo" (el mapa se re-centra automáticamente sobre el niño).
|
||||
|
||||
### Acciones del mapa
|
||||
|
||||
- **legacy_location_map_actions_expanded** — El usuario abrió o cerró el menú de acciones del mapa.
|
||||
- **legacy_location_map_zoomed** — El usuario hizo zoom y se quedó en ese nivel.
|
||||
- **legacy_location_map_style_changed** — El usuario eligió otro estilo de mapa (estándar, claro, oscuro, satélite, etc.).
|
||||
- **legacy_location_map_center_tapped** — El usuario tocó "centrar en el dispositivo" para volver el mapa sobre el niño.
|
||||
- **legacy_location_map_refresh_tapped** — El usuario tocó refrescar dentro del mapa para pedir la posición más reciente.
|
||||
- **legacy_location_shared** — El usuario compartió la ubicación del niño hacia otra app (familia, pareja, abuelos). Acción viral del producto.
|
||||
- **legacy_location_list_sheet_opened** — El usuario abrió la lista con todas sus zonas, lugares frecuentes e historial.
|
||||
- **legacy_location_history_type_filter_changed** — El usuario filtró el historial por tipo de posición (GPS, WiFi, SOS). Filtrar SOS suele indicar que está investigando un evento crítico.
|
||||
282
apps/mobile_app/docs/videocall-integration.md
Normal file
282
apps/mobile_app/docs/videocall-integration.md
Normal file
@@ -0,0 +1,282 @@
|
||||
# Integración Videollamadas — Juphoon jc_sdk
|
||||
|
||||
## Estado general
|
||||
|
||||
| Fase | Estado |
|
||||
|---|---|
|
||||
| 1. SDK wrapper (`videocall_sdk`) | ✅ Completado |
|
||||
| 2. Configuración nativa (permisos) | ✅ Completado |
|
||||
| 3. Configuración por entorno (AppKey) | ✅ Completado |
|
||||
| 4. Feature videocall (UI + lógica) | ⏳ Pendiente |
|
||||
| 5. Pruebas APP↔APP | ⏳ Pendiente |
|
||||
| 6. Integración con backend (señalización) | ⏳ Pendiente |
|
||||
| 7. Pruebas APP↔Reloj | ⏳ Pendiente |
|
||||
| 8. Token auth (producción) | ⏳ Pendiente |
|
||||
| 9. Push/background iOS | ⏳ Pendiente (sin doc del proveedor) |
|
||||
| 10. Chat | ⏳ Pendiente (sin spec del proveedor) |
|
||||
|
||||
---
|
||||
|
||||
## Fase 1: SDK wrapper — ✅
|
||||
|
||||
Paquete `packages/videocall_sdk/` con wrap 100% de `jc_sdk` v2.16.5.
|
||||
|
||||
### Arquitectura (patrón sca_treezor)
|
||||
- Constructor injection (no singletons estáticos)
|
||||
- GetIt module (`videocallSdkModule(config)`)
|
||||
- `VideocallSdkManager` orquestador de inicialización
|
||||
- `VideocallSdkConfig` abstracto para config por entorno
|
||||
- Riverpod providers + StreamProviders para UI reactiva
|
||||
- Callbacks del SDK → Dart Streams
|
||||
|
||||
### Servicios (7 total, cobertura 100%)
|
||||
- `VideocallClient` → JCClient (auth, login, logout, messaging)
|
||||
- `VideocallCallService` → JCCall (llamadas 1-to-1)
|
||||
- `VideocallDeviceService` → JCMediaDevice (cámara, mic, speaker)
|
||||
- `VideocallChannelService` → JCMediaChannel (llamadas grupales)
|
||||
- `VideocallPushService` → JCPush (push notifications)
|
||||
- `VideocallNetService` → JCNet (estado de red)
|
||||
- `VideocallLogService` → JCLog (logging)
|
||||
|
||||
### Estructura
|
||||
```
|
||||
packages/videocall_sdk/lib/src/
|
||||
├── config/videocall_sdk_config.dart
|
||||
├── di/videocall_sdk_module.dart
|
||||
├── manager/videocall_sdk_manager.dart
|
||||
├── models/ (call_state, call_direction, videocall_item, etc.)
|
||||
├── services/ (7 servicios)
|
||||
└── providers/videocall_providers.dart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fase 2: Permisos nativos — ✅
|
||||
|
||||
### Android (`AndroidManifest.xml`)
|
||||
- [x] INTERNET (ya existía)
|
||||
- [x] ACCESS_NETWORK_STATE (ya existía)
|
||||
- [x] ACCESS_WIFI_STATE
|
||||
- [x] CAMERA (ya existía)
|
||||
- [x] RECORD_AUDIO
|
||||
- [x] MODIFY_AUDIO_SETTINGS
|
||||
- [x] BLUETOOTH
|
||||
- [x] uses-feature: hardware.camera
|
||||
- [x] uses-feature: hardware.camera.autofocus
|
||||
- [x] uses-feature: hardware.bluetooth (optional)
|
||||
|
||||
### Android (`proguard-rules.pro`)
|
||||
- [x] -keep com.juphoon.**
|
||||
- [x] -keep com.justalk.**
|
||||
- [x] -keepattributes InnerClasses
|
||||
|
||||
### iOS (`Info.plist`)
|
||||
- [x] NSCameraUsageDescription (actualizado: QR + videollamadas)
|
||||
- [x] NSMicrophoneUsageDescription
|
||||
- [x] NSPhotoLibraryUsageDescription
|
||||
|
||||
### iOS (`Podfile`)
|
||||
- [x] PERMISSION_CAMERA=1
|
||||
- [x] PERMISSION_PHOTOS=1
|
||||
- [x] PERMISSION_MICROPHONE=1
|
||||
|
||||
---
|
||||
|
||||
## Fase 3: Config por entorno — ✅
|
||||
|
||||
- [x] `juphoonAppKey` en development.json, staging.json, production.json
|
||||
- [x] `Environment.juphoonAppKey` via `String.fromEnvironment()`
|
||||
- [x] `SaveFamilyVideocallConfig` implementa `VideocallSdkConfig`
|
||||
- [x] `videocallSdkModule(config)` llamado en `init_app.dart`
|
||||
- [ ] AppKeys separadas por entorno (por ahora las 3 usan la misma clave de dev)
|
||||
|
||||
---
|
||||
|
||||
## Fase 4: Feature videocall — ⏳ SIGUIENTE
|
||||
|
||||
Feature en `modules/legacy/modules/device_management/lib/src/features/videocall/`
|
||||
|
||||
### Por hacer
|
||||
- [ ] `videocall_builder.dart` — GoRouter builder
|
||||
- [ ] `domain/entities/videocall_entity.dart` — Freezed entity
|
||||
- [ ] `domain/entities/videocall_error.dart` — Error enum con i18n
|
||||
- [ ] `presentation/state/videocall_view_model.dart` — Notifier
|
||||
- [ ] `presentation/state/videocall_view_state.dart` — Freezed state
|
||||
- [ ] `presentation/videocall_screen.dart` — Pantalla de llamada
|
||||
- [ ] `presentation/widgets/local_video_view.dart` — Video local
|
||||
- [ ] `presentation/widgets/remote_video_view.dart` — Video remoto
|
||||
- [ ] `presentation/widgets/call_controls.dart` — Botones (colgar, mute, cámara)
|
||||
- [ ] `presentation/widgets/incoming_call_dialog.dart` — Dialog llamada entrante
|
||||
- [ ] Providers en `core/providers/`
|
||||
- [ ] Ruta en GoRouter (`app_router.dart`)
|
||||
- [ ] Runtime permissions (pedir cámara/mic en runtime)
|
||||
|
||||
---
|
||||
|
||||
## Fase 5: Pruebas APP↔APP — ⏳
|
||||
|
||||
- [ ] Login con 2 userIDs de prueba (`p_test1`, `p_test2`)
|
||||
- [ ] Llamada de voz APP→APP
|
||||
- [ ] Videollamada APP→APP
|
||||
- [ ] Responder llamada entrante
|
||||
- [ ] Rechazar llamada
|
||||
- [ ] Colgar durante llamada
|
||||
- [ ] Mute/unmute micrófono
|
||||
- [ ] Cambiar cámara frontal/trasera
|
||||
- [ ] Speaker on/off
|
||||
- [ ] Llamada perdida (onMissedCallItem)
|
||||
- [ ] Verificar desfase versión SDK (quickstart 1.0.2 vs pub.dev 2.16.5)
|
||||
|
||||
---
|
||||
|
||||
## Fase 6: Integración backend — ⏳
|
||||
|
||||
- [ ] Obtener documentación API REST del backend SaveFamily para señalización
|
||||
- [ ] Endpoint para iniciar llamada → notificar al reloj
|
||||
- [ ] Endpoint para recibir notificación de llamada entrante del reloj
|
||||
- [ ] Formato userID definido con backend (`p_<cuenta>` vs `w_<IMEI>`)
|
||||
- [ ] Sanitización de emails (@ → _ en userIDs)
|
||||
|
||||
---
|
||||
|
||||
## Fase 7: Pruebas APP↔Reloj — ⏳
|
||||
|
||||
- [ ] Llamada APP → Reloj
|
||||
- [ ] Llamada Reloj → APP
|
||||
- [ ] Videollamada grupal (JCMediaChannel)
|
||||
- [ ] Límite 5 min de llamada
|
||||
- [ ] Registro IMEI (protocolo RYIMEI) — lo hace el backend
|
||||
|
||||
---
|
||||
|
||||
## Fase 8: Token auth — ⏳
|
||||
|
||||
- [ ] Backend implementa generación de tokens Juphoon (usa AppSecret)
|
||||
- [ ] App pide token al backend antes del login
|
||||
- [ ] Token se pasa como `password` en `VideocallClient.login()`
|
||||
- [ ] Activar Token鉴权 en consola Juphoon (ya está activo)
|
||||
|
||||
Nota: para dev/testing no es necesario — `autoCreateAccount = true` en LoginParam permite login con cualquier password.
|
||||
|
||||
---
|
||||
|
||||
## Fase 9: Push/Background iOS — ⏳ RIESGO
|
||||
|
||||
**Problema:** No hay documentación de cómo recibir llamadas con la app cerrada en iOS.
|
||||
|
||||
- [ ] Probar qué pasa cuando la app está cerrada y llega una llamada (fase 5)
|
||||
- [ ] Si no funciona: investigar PushKit + CallKit
|
||||
- [ ] Verificar si `JCPush` del SDK resuelve esto
|
||||
- [ ] Consultar pestaña "消息通知服务" en la consola Juphoon
|
||||
- [ ] Si es deal-breaker: escalar antes del pago ($8,835)
|
||||
|
||||
---
|
||||
|
||||
## Fase 10: Chat — ⏳ SIN SPEC
|
||||
|
||||
- [ ] Obtener especificación del módulo de chat del proveedor
|
||||
- [ ] Determinar si usa JCMediaChannel (SDK) o API propia
|
||||
- [ ] $2,950 pagados sin lista de features
|
||||
|
||||
---
|
||||
|
||||
## Credenciales Juphoon Cloud
|
||||
|
||||
| Campo | Valor | Quién lo usa |
|
||||
|---|---|---|
|
||||
| AppKey | `9efcf2d889dc8a0320925096` | App Flutter + Backend |
|
||||
| AppSecret | `ui7pr73ggl5rr0gf01np` | Solo Backend |
|
||||
| AES_KEY (IoT) | `8e3637pG7E9144E0` | Solo Backend |
|
||||
| Consola | juphoon.com (+34 603675786) | Julián |
|
||||
|
||||
---
|
||||
|
||||
## Naming conventions (protocolo TCP)
|
||||
|
||||
| Tipo | Formato | Ejemplo |
|
||||
|---|---|---|
|
||||
| Watch userID | `w_` + IMEI | `w_000078932675810` |
|
||||
| Mobile userID | `p_` + APP account | `p_abc10086` |
|
||||
| Group room | `did` + `_group` | `0245423235_group` |
|
||||
| Single room | `did` + `_` + APP account | `0245423235_abc10086` |
|
||||
|
||||
Nota: `@` y `.` se reemplazan por `_` en room numbers y userIDs.
|
||||
|
||||
---
|
||||
|
||||
## Documentación de referencia
|
||||
|
||||
- Quickstart V1.1: `~/Downloads/Video call API_ Juphoon Flutter SDK quickstart V1.1.pdf`
|
||||
- TCP Protocol: `~/Downloads/Juphoon Video Call TCP Protocol.docx`
|
||||
- Connection process: `~/Downloads/video call connection process Rev2.docx`
|
||||
- Mutual dialing: `~/Downloads/video call mutual dialing process.docx`
|
||||
- Schematics: `~/Downloads/schematics _2025.03.26 (2)/`
|
||||
- pub.dev: https://pub.dev/packages/jc_sdk
|
||||
- Consola: https://developer.juphoon.com
|
||||
|
||||
---
|
||||
|
||||
## Flujos de llamada (protocolo TCP + Juphoon SDK)
|
||||
|
||||
### APP → Reloj (outgoing)
|
||||
|
||||
1. App envía `VIDEO_CALL_REQUEST` al backend con `chatType`, `appAccount`, `roomNumber`, `sessionId`
|
||||
2. Backend reenvía la notificación al reloj via TCP
|
||||
3. App inicia audio/cámara y llama al watch account via SDK (`startCall(userId: "w_<IMEI>")`)
|
||||
4. Reloj contesta → SDK notifica via `callItemUpdateStream` (estado `isTalking`)
|
||||
5. App envía `VIDEO_CALL_ROOM_COUNT_REQUEST` con `type` (0/1), `count: 2`, `room_num`
|
||||
|
||||
### Reloj → APP (incoming)
|
||||
|
||||
1. Reloj envía notificación de llamada al backend
|
||||
2. Backend notifica a la app (requiere app abierta con SDK inicializado, ver Fase 9)
|
||||
3. SDK detecta llamada entrante via `callItemAddStream` con `CallDirection.incoming`
|
||||
4. Usuario acepta → `answerCall()` → SDK conecta
|
||||
5. Reloj reporta participantes al backend
|
||||
|
||||
### Colgar / Rechazar
|
||||
|
||||
- **Colgar (en llamada):** `hangUp()` en SDK + `VIDEO_CALL_CANCEL` al backend
|
||||
- **Rechazar (incoming):** `hangUp()` en SDK + `VIDEO_CALL_REFUSE` al backend con `appAccount`, `roomNumber`
|
||||
|
||||
### Convenciones de nombres
|
||||
|
||||
| Campo | Formato | Ejemplo |
|
||||
|---|---|---|
|
||||
| Watch userID | `w_` + IMEI | `w_000078932675810` |
|
||||
| Mobile userID | `p_` + email sanitizado | `p_user_example_com` |
|
||||
| Room (single) | `deviceId` + `_` + appAccount | `0245423235_p_user_example_com` |
|
||||
| Room (group) | `deviceId` + `_group` | `0245423235_group` |
|
||||
| Session ID | `deviceId` + `_` + epoch en segundos | `0245423235_1714150800` |
|
||||
|
||||
Sanitización: `@` y `.` se reemplazan por `_` en userIDs y roomNumbers.
|
||||
|
||||
### Configuración del SDK por tipo de dispositivo
|
||||
|
||||
- RTOS watches: `MediaConfig.MODE_RTOS`
|
||||
- Android watches: `MediaConfig.MODE_INTELLIGENT_HARDWARE`
|
||||
- Se determina con `device.capabilities.system` (`isRtos` / `isAndroid`)
|
||||
|
||||
### Auto-login
|
||||
|
||||
- userId: `p_` + email sanitizado (ej: `p_julian_test_com`)
|
||||
- password: `user.id` (UUID del usuario padre)
|
||||
- En dev/testing `autoCreateAccount = true` permite login con cualquier password
|
||||
|
||||
---
|
||||
|
||||
## Limitaciones actuales
|
||||
|
||||
### Recepción de llamadas requiere app abierta
|
||||
|
||||
La app debe estar en primer plano con el SDK inicializado y el client logueado para recibir llamadas entrantes. Si el app está en background o cerrada, las llamadas no llegan. Esto se resuelve en Fase 9 (Push/Background).
|
||||
|
||||
### Sin timeout de llamada
|
||||
|
||||
El protocolo menciona un límite de 5 min por llamada, pero no está implementado en la app. El reloj podría manejar el corte por su lado.
|
||||
|
||||
---
|
||||
|
||||
## Pendientes por verificar
|
||||
|
||||
- **chatType**: El protocolo TCP usa `0` (single) y `1` (multi) como enteros. Nuestra app envía `"single"`/`"multi"` como strings en el JSON del comando. Verificar que el backend hace la conversión correctamente antes de enviar al reloj via TCP.
|
||||
1
apps/mobile_app/firebase.json
Normal file
1
apps/mobile_app/firebase.json
Normal file
@@ -0,0 +1 @@
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"sf-platform-pre","appId":"1:535646668726:android:b87245b807258e3e5e6317","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"sf-platform-pre","appId":"1:535646668726:ios:5172d626d02dfe215e6317","uploadDebugSymbols":true,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options_dev.dart":{"projectId":"sf-platform-pre","configurations":{"android":"1:535646668726:android:c3a09d6c26f0cdf95e6317","ios":"1:535646668726:ios:524afa641f61d7cb5e6317"}},"lib/firebase_options_staging.dart":{"projectId":"sf-platform-pre","configurations":{"android":"1:535646668726:android:b87245b807258e3e5e6317","ios":"1:535646668726:ios:5172d626d02dfe215e6317"}},"lib/firebase_options_prod.dart":{"projectId":"sf-platform-pro","configurations":{"android":"1:950566980029:android:75a7c10b6259d09681aad4","ios":"1:950566980029:ios:987b4f0b9e9b897481aad4"}}}}}}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '13.4'
|
||||
platform :ios, '15.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
@@ -45,5 +45,14 @@ end
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
## Juphoon jc_sdk: enable camera, photos and microphone permissions
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||
'$(inherited)',
|
||||
'PERMISSION_CAMERA=1',
|
||||
'PERMISSION_PHOTOS=1',
|
||||
'PERMISSION_MICROPHONE=1'
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,20 +1,250 @@
|
||||
PODS:
|
||||
- audioplayers_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Firebase/CoreOnly (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- Firebase/Crashlytics (12.9.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseCrashlytics (~> 12.9.0)
|
||||
- Firebase/Messaging (12.9.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseMessaging (~> 12.9.0)
|
||||
- Firebase/Performance (12.9.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebasePerformance (~> 12.9.0)
|
||||
- Firebase/RemoteConfig (12.9.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseRemoteConfig (~> 12.9.0)
|
||||
- firebase_analytics (12.2.0):
|
||||
- firebase_core
|
||||
- FirebaseAnalytics (= 12.9.0)
|
||||
- Flutter
|
||||
- firebase_core (4.6.0):
|
||||
- Firebase/CoreOnly (= 12.9.0)
|
||||
- Flutter
|
||||
- firebase_crashlytics (5.1.0):
|
||||
- Firebase/Crashlytics (= 12.9.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- firebase_messaging (16.1.3):
|
||||
- Firebase/Messaging (= 12.9.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- firebase_performance (0.11.2):
|
||||
- Firebase/Performance (= 12.9.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- firebase_remote_config (6.3.0):
|
||||
- Firebase/RemoteConfig (= 12.9.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- FirebaseABTesting (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseAnalytics (12.9.0):
|
||||
- FirebaseAnalytics/Default (= 12.9.0)
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseInstallations (~> 12.9.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseAnalytics/Default (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseInstallations (~> 12.9.0)
|
||||
- GoogleAppMeasurement/Default (= 12.9.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseCore (12.9.0):
|
||||
- FirebaseCoreInternal (~> 12.9.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Logger (~> 8.1)
|
||||
- FirebaseCoreExtension (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseCoreInternal (12.9.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- FirebaseCrashlytics (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseInstallations (~> 12.9.0)
|
||||
- FirebaseRemoteConfigInterop (~> 12.9.0)
|
||||
- FirebaseSessions (~> 12.9.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseInstallations (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseMessaging (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseInstallations (~> 12.9.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Reachability (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebasePerformance (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseInstallations (~> 12.9.0)
|
||||
- FirebaseRemoteConfig (~> 12.9.0)
|
||||
- FirebaseSessions (~> 12.9.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseRemoteConfig (12.9.0):
|
||||
- FirebaseABTesting (~> 12.9.0)
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseInstallations (~> 12.9.0)
|
||||
- FirebaseRemoteConfigInterop (~> 12.9.0)
|
||||
- FirebaseSharedSwift (~> 12.9.0)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- FirebaseRemoteConfigInterop (12.9.0)
|
||||
- FirebaseSessions (12.9.0):
|
||||
- FirebaseCore (~> 12.9.0)
|
||||
- FirebaseCoreExtension (~> 12.9.0)
|
||||
- FirebaseInstallations (~> 12.9.0)
|
||||
- GoogleDataTransport (~> 10.1)
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesSwift (~> 2.1)
|
||||
- FirebaseSharedSwift (12.9.0)
|
||||
- Flutter (1.0.0)
|
||||
- flutter_contacts (0.0.1):
|
||||
- Flutter
|
||||
- flutter_image_compress_common (1.0.0):
|
||||
- Flutter
|
||||
- Mantle
|
||||
- SDWebImage
|
||||
- SDWebImageWebPCoder
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- flutter_native_splash (2.4.3):
|
||||
- Flutter
|
||||
- flutter_ringtone_player (0.0.1):
|
||||
- Flutter
|
||||
- flutter_treezor_entrust_sdk_bridge (0.0.1):
|
||||
- Flutter
|
||||
- GoogleAdsOnDeviceConversion (3.2.0):
|
||||
- GoogleUtilities/Environment (~> 8.1)
|
||||
- GoogleUtilities/Logger (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/Core (12.9.0):
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/Default (12.9.0):
|
||||
- GoogleAdsOnDeviceConversion (~> 3.2.0)
|
||||
- GoogleAppMeasurement/Core (= 12.9.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (= 12.9.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleAppMeasurement/IdentitySupport (12.9.0):
|
||||
- GoogleAppMeasurement/Core (= 12.9.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||
- GoogleUtilities/Network (~> 8.1)
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||
- nanopb (~> 3.30910.0)
|
||||
- GoogleDataTransport (10.1.0):
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Environment (8.1.0):
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Logger (8.1.0):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/MethodSwizzler (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Network (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- "GoogleUtilities/NSData+zlib"
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Reachability
|
||||
- "GoogleUtilities/NSData+zlib (8.1.0)":
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (8.1.0)
|
||||
- GoogleUtilities/Reachability (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/UserDefaults (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- jc_sdk (0.0.1):
|
||||
- Flutter
|
||||
- libwebp (1.5.0):
|
||||
- libwebp/demux (= 1.5.0)
|
||||
- libwebp/mux (= 1.5.0)
|
||||
- libwebp/sharpyuv (= 1.5.0)
|
||||
- libwebp/webp (= 1.5.0)
|
||||
- libwebp/demux (1.5.0):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.5.0):
|
||||
- libwebp/demux
|
||||
- libwebp/sharpyuv (1.5.0)
|
||||
- libwebp/webp (1.5.0):
|
||||
- libwebp/sharpyuv
|
||||
- Mantle (2.2.0):
|
||||
- Mantle/extobjc (= 2.2.0)
|
||||
- Mantle/extobjc (2.2.0)
|
||||
- mobile_scanner (7.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- nanopb (3.30910.0):
|
||||
- nanopb/decode (= 3.30910.0)
|
||||
- nanopb/encode (= 3.30910.0)
|
||||
- nanopb/decode (3.30910.0)
|
||||
- nanopb/encode (3.30910.0)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- PromisesObjC (2.4.0)
|
||||
- PromisesSwift (2.4.0):
|
||||
- PromisesObjC (= 2.4.0)
|
||||
- record_ios (1.2.0):
|
||||
- Flutter
|
||||
- SDWebImage (5.21.7):
|
||||
- SDWebImage/Core (= 5.21.7)
|
||||
- SDWebImage/Core (5.21.7)
|
||||
- SDWebImageWebPCoder (0.15.0):
|
||||
- libwebp (~> 1.0)
|
||||
- SDWebImage/Core (~> 5.17)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- webview_flutter_wkwebview (0.0.1):
|
||||
@@ -22,47 +252,168 @@ PODS:
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
|
||||
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
- firebase_performance (from `.symlinks/plugins/firebase_performance/ios`)
|
||||
- firebase_remote_config (from `.symlinks/plugins/firebase_remote_config/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_contacts (from `.symlinks/plugins/flutter_contacts/ios`)
|
||||
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- flutter_ringtone_player (from `.symlinks/plugins/flutter_ringtone_player/ios`)
|
||||
- flutter_treezor_entrust_sdk_bridge (from `.symlinks/plugins/flutter_treezor_entrust_sdk_bridge/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- jc_sdk (from `.symlinks/plugins/jc_sdk/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- record_ios (from `.symlinks/plugins/record_ios/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Firebase
|
||||
- FirebaseABTesting
|
||||
- FirebaseAnalytics
|
||||
- FirebaseCore
|
||||
- FirebaseCoreExtension
|
||||
- FirebaseCoreInternal
|
||||
- FirebaseCrashlytics
|
||||
- FirebaseInstallations
|
||||
- FirebaseMessaging
|
||||
- FirebasePerformance
|
||||
- FirebaseRemoteConfig
|
||||
- FirebaseRemoteConfigInterop
|
||||
- FirebaseSessions
|
||||
- FirebaseSharedSwift
|
||||
- GoogleAdsOnDeviceConversion
|
||||
- GoogleAppMeasurement
|
||||
- GoogleDataTransport
|
||||
- GoogleUtilities
|
||||
- libwebp
|
||||
- Mantle
|
||||
- nanopb
|
||||
- PromisesObjC
|
||||
- PromisesSwift
|
||||
- SDWebImage
|
||||
- SDWebImageWebPCoder
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
audioplayers_darwin:
|
||||
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
|
||||
firebase_analytics:
|
||||
:path: ".symlinks/plugins/firebase_analytics/ios"
|
||||
firebase_core:
|
||||
:path: ".symlinks/plugins/firebase_core/ios"
|
||||
firebase_crashlytics:
|
||||
:path: ".symlinks/plugins/firebase_crashlytics/ios"
|
||||
firebase_messaging:
|
||||
:path: ".symlinks/plugins/firebase_messaging/ios"
|
||||
firebase_performance:
|
||||
:path: ".symlinks/plugins/firebase_performance/ios"
|
||||
firebase_remote_config:
|
||||
:path: ".symlinks/plugins/firebase_remote_config/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_contacts:
|
||||
:path: ".symlinks/plugins/flutter_contacts/ios"
|
||||
flutter_image_compress_common:
|
||||
:path: ".symlinks/plugins/flutter_image_compress_common/ios"
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
flutter_native_splash:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
flutter_ringtone_player:
|
||||
:path: ".symlinks/plugins/flutter_ringtone_player/ios"
|
||||
flutter_treezor_entrust_sdk_bridge:
|
||||
:path: ".symlinks/plugins/flutter_treezor_entrust_sdk_bridge/ios"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
jc_sdk:
|
||||
:path: ".symlinks/plugins/jc_sdk/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
record_ios:
|
||||
:path: ".symlinks/plugins/record_ios/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
webview_flutter_wkwebview:
|
||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
audioplayers_darwin: f15e209a3e856d1a7edcf98dc029f484fead2242
|
||||
Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
|
||||
firebase_analytics: 42693ebf35c4d330b74abcb46ca80351703644e0
|
||||
firebase_core: 98bcc1bd1a097bcb8b1ed6e091de3039802527c4
|
||||
firebase_crashlytics: 2fd6c030ca2f91e8d3b13d2e6e9a08a282c9d259
|
||||
firebase_messaging: e24e69d994d53e46fd794143544841877bd85a53
|
||||
firebase_performance: 39d7f9632628c64cacd9e9808d4783cffd83eaa2
|
||||
firebase_remote_config: 0d060eef0fdfb288ffc41903ba9a60bb963755ea
|
||||
FirebaseABTesting: a399ffe546392a39b19a5c2fb28bd8ea178a6f47
|
||||
FirebaseAnalytics: cd7d01d352f3c237c9a0e31552c257cd0b0c0352
|
||||
FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8
|
||||
FirebaseCoreExtension: e911052d59cd0da237a45d706fc0f81654f035c1
|
||||
FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72
|
||||
FirebaseCrashlytics: 43913d587ef07beaf5db703baa61eacf9554658c
|
||||
FirebaseInstallations: 7b64ffd006032b2b019a59b803858df5112d9eaa
|
||||
FirebaseMessaging: 7d6cdbff969127c4151c824fe432f0e301210f15
|
||||
FirebasePerformance: 94f614453614d8bb2a1a0177f3a1a6d2dbf4c504
|
||||
FirebaseRemoteConfig: a2f6545e41551ffb520241d38b5d3d6776c9ebe8
|
||||
FirebaseRemoteConfigInterop: 765ee19cd2bfa8e54937c8dae901eb634ad6787d
|
||||
FirebaseSessions: a2d06fd980431fda934c7a543901aca05fc4edcc
|
||||
FirebaseSharedSwift: 9d2fa84a46676302b89dbd5e6e62bce2fe376909
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_contacts: edb1c5ce76aa433e20e6cb14c615f4c0b66e0983
|
||||
flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e
|
||||
flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f
|
||||
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||
flutter_ringtone_player: 15eba85187230b87b2512f0e1b92225618bc03e7
|
||||
flutter_treezor_entrust_sdk_bridge: 4c2c94fb74ab57576e8d49f5f2a4b214e41141fe
|
||||
GoogleAdsOnDeviceConversion: d68c69dd9581a0f5da02617b6f377e5be483970f
|
||||
GoogleAppMeasurement: fce7c1c90640d2f9f5c56771f71deacb2ba3f98c
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
|
||||
jc_sdk: 3c77f6d7e5e052e2960c47629f612127585779cf
|
||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
||||
record_ios: 26294aaa39e4bb7665b0fef78bdc23d723b432f2
|
||||
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
|
||||
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
|
||||
webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4
|
||||
|
||||
PODFILE CHECKSUM: 02dccdf227cb9aef09ff0299e4898a8a19004223
|
||||
PODFILE CHECKSUM: 88fd88ec59f7f53cf74c06ffd99479aec395968a
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
AA0000011234567800000001 /* AntelopRelease.plist in Resources */ = {isa = PBXBuildFile; fileRef = AA0000011234567800000002 /* AntelopRelease.plist */; };
|
||||
AA5000010000000000000001 /* AntelopRelease-development.plist in Resources */ = {isa = PBXBuildFile; fileRef = AA5000010000000000000002 /* AntelopRelease-development.plist */; };
|
||||
AA5000010000000000000003 /* AntelopRelease-staging.plist in Resources */ = {isa = PBXBuildFile; fileRef = AA5000010000000000000004 /* AntelopRelease-staging.plist */; };
|
||||
D6B9158A899AF56C44180233 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B8D66015CBEA02CDD29EB55 /* GoogleService-Info.plist */; };
|
||||
FB256274E508EC552E337980 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B56AB2467FA9548370ACF02 /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -53,6 +54,7 @@
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
3B8D66015CBEA02CDD29EB55 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
401E1064C971570DADB8AA9B /* Pods-RunnerTests.profile-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile-development.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile-development.xcconfig"; sourceTree = "<group>"; };
|
||||
4B56AB2467FA9548370ACF02 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4E688A593FA9E76BDD0DFBFB /* Pods-Runner.debug-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-staging.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-staging.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -144,6 +146,7 @@
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
CB8808A12E373F2255B5FC16 /* Pods */,
|
||||
BE496D7F3574271661ADBDCE /* Frameworks */,
|
||||
3B8D66015CBEA02CDD29EB55 /* GoogleService-Info.plist */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -252,8 +255,11 @@
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
F0758EB530B1A8787EB3F30B /* Copy GoogleService-Info */,
|
||||
AA0000022345678900000001 /* Copy AntelopRelease */,
|
||||
437F5EA1E5D92D7C421FD996 /* [CP] Embed Pods Frameworks */,
|
||||
791C3CA41F1AAEE1267769C8 /* [CP] Copy Pods Resources */,
|
||||
0F0F4E82D9AA0B3E11014E72 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -322,12 +328,31 @@
|
||||
AA0000011234567800000001 /* AntelopRelease.plist in Resources */,
|
||||
AA5000010000000000000001 /* AntelopRelease-development.plist in Resources */,
|
||||
AA5000010000000000000003 /* AntelopRelease-staging.plist in Resources */,
|
||||
D6B9158A899AF56C44180233 /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
0F0F4E82D9AA0B3E11014E72 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\"";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:${PUB_CACHE}/bin:$HOME/.pub-cache/bin\"\n\nif [ -z \"$PODS_ROOT\" ] || [ ! -d \"$PODS_ROOT/FirebaseCrashlytics\" ]; then\n # Cannot use \"BUILD_DIR%/Build/*\" as per Firebase documentation, it points to \"flutter-project/build/ios/*\" path which doesn't have run script\n DERIVED_DATA_PATH=$(echo \"$BUILD_ROOT\" | sed -E 's|(.*DerivedData/[^/]+).*|\\1|')\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\nelse\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"$PODS_ROOT/FirebaseCrashlytics/run\"\nfi\n\n# Command to upload symbols script used to upload symbols to Firebase server\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n";
|
||||
};
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
@@ -437,6 +462,42 @@
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
F0758EB530B1A8787EB3F30B /* Copy GoogleService-Info */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Copy GoogleService-Info";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/scripts/copy-google-service-plist.sh\"";
|
||||
};
|
||||
AA0000022345678900000001 /* Copy AntelopRelease */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Copy AntelopRelease";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/scripts/copy-antelop-release-plist.sh\"";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -530,7 +591,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -543,7 +604,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-development";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Runner/Runner-development.entitlements";
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
@@ -661,7 +722,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -712,7 +773,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -727,7 +788,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-development";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Runner/Runner-development.entitlements";
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
@@ -751,7 +812,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-development";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Runner/Runner-development.entitlements";
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
@@ -819,7 +880,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -876,7 +937,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -927,7 +988,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -981,7 +1042,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -1035,7 +1096,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -1087,7 +1148,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -1100,7 +1161,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-staging";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Runner/Runner-staging.entitlements";
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
@@ -1124,7 +1185,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-production";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
@@ -1148,7 +1209,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-staging";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Runner/Runner-staging.entitlements";
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
@@ -1171,7 +1232,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-production";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
@@ -1194,7 +1255,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-staging";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Runner/Runner-staging.entitlements";
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
@@ -1217,7 +1278,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-production";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
|
||||
@@ -23,12 +23,16 @@ import AntelopSDK
|
||||
|
||||
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
AntelopAppDelegate.shared.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
|
||||
// Forward to FlutterAppDelegate so Firebase Messaging can capture the APNs token via swizzling.
|
||||
super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
|
||||
}
|
||||
|
||||
override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
||||
guard !AntelopAppDelegate.shared.didReceiveRemoteNotification(userInfo, fetchCompletionHandler: completionHandler) else {
|
||||
if AntelopAppDelegate.shared.didReceiveRemoteNotification(userInfo, fetchCompletionHandler: completionHandler) {
|
||||
return
|
||||
}
|
||||
// Forward to FlutterAppDelegate so Firebase Messaging can deliver the notification to Dart.
|
||||
super.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
||||
|
||||
30
apps/mobile_app/ios/Runner/GoogleService-Info.plist
Normal file
30
apps/mobile_app/ios/Runner/GoogleService-Info.plist
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyBeijehJIznndwIUlbMkj6reYT4z-WHGfQ</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>535646668726</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.savefamily.app.dev</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>sf-platform-pre</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>sf-platform-pre.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:535646668726:ios:524afa641f61d7cb5e6317</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -47,8 +47,14 @@
|
||||
<true/>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>Necesitamos acceso a tus contactos para seleccionar números de teléfono.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Necesitamos la cámara para escanear códigos QR</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Usamos tu ubicación para verificar la seguridad de las transacciones.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>Usamos tu ubicación para verificar la seguridad de las transacciones.</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
|
||||
@@ -47,8 +47,14 @@
|
||||
<true/>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>Necesitamos acceso a tus contactos para seleccionar números de teléfono.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Necesitamos la cámara para escanear códigos QR</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Usamos tu ubicación para verificar la seguridad de las transacciones.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>Usamos tu ubicación para verificar la seguridad de las transacciones.</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
|
||||
@@ -47,8 +47,14 @@
|
||||
<true/>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>Necesitamos acceso a tus contactos para seleccionar números de teléfono.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Necesitamos la cámara para escanear códigos QR</string>
|
||||
<string>Necesitamos la cámara para escanear códigos QR y realizar videollamadas</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Necesitamos el micrófono para realizar videollamadas</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Necesitamos acceso a la galería de fotos para compartir imágenes</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Usamos tu ubicación para verificar la seguridad de las transacciones.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.savefamily.app.stag</string>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>production</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.savefamily.app.prod</string>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyBeijehJIznndwIUlbMkj6reYT4z-WHGfQ</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>535646668726</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.savefamily.app.dev</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>sf-platform-pre</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>sf-platform-pre.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:535646668726:ios:524afa641f61d7cb5e6317</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyC0_d7Z6uVHHKhaf7JHRROaY6g2mvvpOXU</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>950566980029</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.savefamily.app</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>sf-platform-pro</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>sf-platform-pro.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:950566980029:ios:987b4f0b9e9b897481aad4</string>
|
||||
</dict>
|
||||
</plist>
|
||||
30
apps/mobile_app/ios/flavors/staging/GoogleService-Info.plist
Normal file
30
apps/mobile_app/ios/flavors/staging/GoogleService-Info.plist
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyBeijehJIznndwIUlbMkj6reYT4z-WHGfQ</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>535646668726</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.savefamily.app.stag</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>sf-platform-pre</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>sf-platform-pre.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:535646668726:ios:5172d626d02dfe215e6317</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env ruby
|
||||
#
|
||||
# Adds a "Copy GoogleService-Info" Run Script Build Phase to the Runner target.
|
||||
# The script copies ios/flavors/{flavor}/GoogleService-Info.plist to the .app
|
||||
# bundle based on the build CONFIGURATION (Debug-development, Release-staging, etc.).
|
||||
#
|
||||
# Idempotent: if the build phase already exists, does nothing.
|
||||
#
|
||||
# Usage:
|
||||
# ruby ios/scripts/add-copy-google-service-build-phase.rb
|
||||
|
||||
require 'xcodeproj'
|
||||
|
||||
PROJECT_PATH = File.expand_path('../../Runner.xcodeproj', __FILE__)
|
||||
TARGET_NAME = 'Runner'
|
||||
PHASE_NAME = 'Copy GoogleService-Info'
|
||||
SHELL_SCRIPT = '"${SRCROOT}/scripts/copy-google-service-plist.sh"'
|
||||
|
||||
project = Xcodeproj::Project.open(PROJECT_PATH)
|
||||
target = project.targets.find { |t| t.name == TARGET_NAME }
|
||||
|
||||
unless target
|
||||
abort "ERROR: Target '#{TARGET_NAME}' not found in project."
|
||||
end
|
||||
|
||||
# Check if the build phase already exists (idempotency)
|
||||
existing = target.build_phases.find do |phase|
|
||||
phase.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && phase.name == PHASE_NAME
|
||||
end
|
||||
|
||||
if existing
|
||||
puts "OK: Build phase '#{PHASE_NAME}' already exists. No changes needed."
|
||||
exit 0
|
||||
end
|
||||
|
||||
# Create the new build phase
|
||||
phase = target.new_shell_script_build_phase(PHASE_NAME)
|
||||
phase.shell_path = '/bin/sh'
|
||||
phase.shell_script = SHELL_SCRIPT
|
||||
phase.input_paths = []
|
||||
phase.output_paths = []
|
||||
phase.run_only_for_deployment_postprocessing = '0'
|
||||
|
||||
# Move it before the embed frameworks phase (or at the end if no such phase)
|
||||
# Order: Sources -> Frameworks -> Resources -> ... -> ThinBinary -> CopyGoogleService -> EmbedPodsFrameworks -> CopyPodsResources
|
||||
build_phases = target.build_phases
|
||||
|
||||
# Find the index of "Thin Binary" if it exists
|
||||
thin_binary_idx = build_phases.find_index do |p|
|
||||
p.respond_to?(:name) && p.name == 'Thin Binary'
|
||||
end
|
||||
|
||||
# Find the index of "[CP] Embed Pods Frameworks" if it exists
|
||||
embed_pods_idx = build_phases.find_index do |p|
|
||||
p.respond_to?(:name) && p.name && p.name.include?('Embed Pods Frameworks')
|
||||
end
|
||||
|
||||
# Remove the just-added phase from its current position (it gets appended at the end)
|
||||
build_phases.delete(phase)
|
||||
|
||||
# Insert at the right spot
|
||||
target_idx = if thin_binary_idx && embed_pods_idx && thin_binary_idx < embed_pods_idx
|
||||
# Place between Thin Binary and Embed Pods Frameworks
|
||||
embed_pods_idx
|
||||
elsif thin_binary_idx
|
||||
# Place right after Thin Binary
|
||||
thin_binary_idx + 1
|
||||
elsif embed_pods_idx
|
||||
# Place right before Embed Pods Frameworks
|
||||
embed_pods_idx
|
||||
else
|
||||
# Append at the end
|
||||
build_phases.length
|
||||
end
|
||||
|
||||
build_phases.insert(target_idx, phase)
|
||||
|
||||
project.save
|
||||
|
||||
puts "OK: Added build phase '#{PHASE_NAME}' at position #{target_idx}."
|
||||
puts "Build phases order:"
|
||||
target.build_phases.each_with_index do |p, i|
|
||||
name = p.respond_to?(:name) && p.name ? p.name : p.class.name
|
||||
puts " #{i}: #{name}"
|
||||
end
|
||||
46
apps/mobile_app/ios/scripts/copy-antelop-release-plist.sh
Executable file
46
apps/mobile_app/ios/scripts/copy-antelop-release-plist.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copies the correct AntelopRelease.plist into the .app bundle based on the
|
||||
# active build CONFIGURATION (Debug-development, Release-staging, etc.). The
|
||||
# Antelop SDK reads `AntelopRelease.plist` by a fixed name at runtime, so the
|
||||
# per-flavor variants (AntelopRelease-development.plist,
|
||||
# AntelopRelease-staging.plist) must be copied over that fixed name.
|
||||
#
|
||||
# Source layout: ios/Runner/AntelopRelease.plist (production),
|
||||
# ios/Runner/AntelopRelease-development.plist,
|
||||
# ios/Runner/AntelopRelease-staging.plist.
|
||||
|
||||
set -e
|
||||
|
||||
echo "Configuration: ${CONFIGURATION}"
|
||||
|
||||
if [[ $CONFIGURATION =~ \-([^-]*)$ ]]; then
|
||||
flavor=${BASH_REMATCH[1]}
|
||||
else
|
||||
echo "warning: Could not extract flavor from CONFIGURATION='${CONFIGURATION}', defaulting to 'production'"
|
||||
flavor="production"
|
||||
fi
|
||||
|
||||
echo "Flavor: $flavor"
|
||||
|
||||
case "$flavor" in
|
||||
development|staging)
|
||||
SRC="${PROJECT_DIR}/Runner/AntelopRelease-${flavor}.plist"
|
||||
;;
|
||||
production)
|
||||
SRC="${PROJECT_DIR}/Runner/AntelopRelease.plist"
|
||||
;;
|
||||
*)
|
||||
echo "warning: Unknown flavor '${flavor}', falling back to AntelopRelease.plist"
|
||||
SRC="${PROJECT_DIR}/Runner/AntelopRelease.plist"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -f "$SRC" ]; then
|
||||
echo "error: ${SRC} not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEST="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/AntelopRelease.plist"
|
||||
echo "Copying ${SRC} -> ${DEST}"
|
||||
cp "${SRC}" "${DEST}"
|
||||
35
apps/mobile_app/ios/scripts/copy-google-service-plist.sh
Executable file
35
apps/mobile_app/ios/scripts/copy-google-service-plist.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copies the correct GoogleService-Info.plist into the .app bundle
|
||||
# based on the active build CONFIGURATION (Debug-development,
|
||||
# Release-staging, etc.). Reads from ios/flavors/{flavor}/GoogleService-Info.plist
|
||||
# and writes to the final bundle.
|
||||
#
|
||||
# Add this as a Run Script Build Phase in Xcode AFTER "Thin Binary" and
|
||||
# BEFORE "[CP] Embed Pods Frameworks" (or near the end of the phases).
|
||||
|
||||
set -e
|
||||
|
||||
echo "Configuration: ${CONFIGURATION}"
|
||||
|
||||
# Extract flavor from the build configuration name (everything after the last "-")
|
||||
if [[ $CONFIGURATION =~ \-([^-]*)$ ]]; then
|
||||
flavor=${BASH_REMATCH[1]}
|
||||
else
|
||||
echo "warning: Could not extract flavor from CONFIGURATION='${CONFIGURATION}', defaulting to 'development'"
|
||||
flavor="development"
|
||||
fi
|
||||
|
||||
echo "Flavor: $flavor"
|
||||
|
||||
GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist
|
||||
GOOGLESERVICE_INFO_FILE="${PROJECT_DIR}/flavors/${flavor}/${GOOGLESERVICE_INFO_PLIST}"
|
||||
|
||||
if [ ! -f "$GOOGLESERVICE_INFO_FILE" ]; then
|
||||
echo "error: ${GOOGLESERVICE_INFO_FILE} not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PLIST_DESTINATION="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app"
|
||||
echo "Copying ${GOOGLESERVICE_INFO_FILE} -> ${PLIST_DESTINATION}/${GOOGLESERVICE_INFO_PLIST}"
|
||||
cp "${GOOGLESERVICE_INFO_FILE}" "${PLIST_DESTINATION}/${GOOGLESERVICE_INFO_PLIST}"
|
||||
@@ -2,6 +2,8 @@ abstract class Environment {
|
||||
static const env = String.fromEnvironment('env', defaultValue: 'development');
|
||||
static const apiBaseUrl = String.fromEnvironment('apiBaseUrl');
|
||||
static const apiOrigin = String.fromEnvironment('apiOrigin');
|
||||
static const wsUrl = String.fromEnvironment('wsUrl');
|
||||
static const juphoonAppKey = String.fromEnvironment('juphoonAppKey');
|
||||
|
||||
// --- Fase 2: Firebase & Sentry ---
|
||||
// static const sentryDsn = String.fromEnvironment('sentryDsn');
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'environment.dart';
|
||||
|
||||
class QuestiaEnvConfig implements EnvConfig {
|
||||
class SaveFamilyEnvConfig implements EnvConfig {
|
||||
@override
|
||||
String get apiBaseUrl => Environment.apiBaseUrl;
|
||||
@override
|
||||
String get apiOrigin => Environment.apiOrigin;
|
||||
@override
|
||||
String get wsUrl => Environment.wsUrl;
|
||||
}
|
||||
14
apps/mobile_app/lib/config/env/save_family_videocall_config.dart
vendored
Normal file
14
apps/mobile_app/lib/config/env/save_family_videocall_config.dart
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:videocall_sdk/videocall_sdk.dart';
|
||||
|
||||
import 'environment.dart';
|
||||
|
||||
class SaveFamilyVideocallConfig implements VideocallSdkConfig {
|
||||
@override
|
||||
String get appKey => Environment.juphoonAppKey;
|
||||
|
||||
@override
|
||||
String get serverAddress => '';
|
||||
|
||||
@override
|
||||
CreateParam? get createParam => null;
|
||||
}
|
||||
3
apps/mobile_app/lib/core/app_provider_container.dart
Normal file
3
apps/mobile_app/lib/core/app_provider_container.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
late ProviderContainer appProviderContainer;
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'app_version_check.dart';
|
||||
|
||||
Future<void> showAppUpdateDialog(
|
||||
BuildContext context, {
|
||||
required AvailableUpdate result,
|
||||
VoidCallback? onDismiss,
|
||||
VoidCallback? onUpdateTapped,
|
||||
}) {
|
||||
final isForce = result is ForceUpdate;
|
||||
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: !isForce,
|
||||
builder: (dialogContext) {
|
||||
return PopScope(
|
||||
canPop: !isForce,
|
||||
child: AlertDialog(
|
||||
title: Text(
|
||||
dialogContext.translate(
|
||||
isForce
|
||||
? I18n.appUpdateRequiredTitle
|
||||
: I18n.appUpdateAvailableTitle,
|
||||
),
|
||||
),
|
||||
content: Text(
|
||||
result.message.isNotEmpty
|
||||
? result.message
|
||||
: dialogContext.translate(
|
||||
isForce
|
||||
? I18n.appUpdateRequiredMessage
|
||||
: I18n.appUpdateAvailableMessage,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (!isForce)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(dialogContext).pop();
|
||||
onDismiss?.call();
|
||||
},
|
||||
child: Text(dialogContext.translate(I18n.appUpdateLater)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => _launchStore(result.storeUrl, onUpdateTapped),
|
||||
child: Text(dialogContext.translate(I18n.appUpdateNow)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _launchStore(String storeUrl, VoidCallback? onTapped) async {
|
||||
onTapped?.call();
|
||||
try {
|
||||
final uri = Uri.tryParse(storeUrl);
|
||||
if (uri == null) {
|
||||
debugPrint('[AppUpdateDialog] invalid store URL: $storeUrl');
|
||||
return;
|
||||
}
|
||||
final launched = await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
if (!launched) {
|
||||
debugPrint('[AppUpdateDialog] launchUrl returned false for $storeUrl');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[AppUpdateDialog] launchUrl failed: $e');
|
||||
}
|
||||
}
|
||||
123
apps/mobile_app/lib/core/app_version_check/app_update_gate.dart
Normal file
123
apps/mobile_app/lib/core/app_version_check/app_update_gate.dart
Normal file
@@ -0,0 +1,123 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_app_platform/navigation/app_router.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
import 'app_update_dialog.dart';
|
||||
import 'app_version_check.dart';
|
||||
|
||||
class AppUpdateGate extends ConsumerStatefulWidget {
|
||||
const AppUpdateGate({super.key, required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
ConsumerState<AppUpdateGate> createState() => _AppUpdateGateState();
|
||||
}
|
||||
|
||||
class _AppUpdateGateState extends ConsumerState<AppUpdateGate> {
|
||||
bool _dialogVisible = false;
|
||||
VoidCallback? _pendingRouterListener;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_detachPendingRouterListener();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _detachPendingRouterListener() {
|
||||
final listener = _pendingRouterListener;
|
||||
if (listener != null) {
|
||||
appRouter.routerDelegate.removeListener(listener);
|
||||
_pendingRouterListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isStableRoute() {
|
||||
final path = appRouter.routerDelegate.currentConfiguration.uri.path;
|
||||
return path.startsWith(AppRoutes.dashboard) ||
|
||||
path.startsWith(AppRoutes.legacyDashboard);
|
||||
}
|
||||
|
||||
void _onResultEmitted(AppVersionCheckResult result) {
|
||||
if (result is! AvailableUpdate) {
|
||||
_detachPendingRouterListener();
|
||||
return;
|
||||
}
|
||||
if (_dialogVisible) return;
|
||||
|
||||
if (_isStableRoute()) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_show(result);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_detachPendingRouterListener();
|
||||
void onChange() {
|
||||
if (!_isStableRoute()) return;
|
||||
_detachPendingRouterListener();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_show(result);
|
||||
});
|
||||
}
|
||||
|
||||
_pendingRouterListener = onChange;
|
||||
appRouter.routerDelegate.addListener(onChange);
|
||||
}
|
||||
|
||||
void _show(AvailableUpdate result) {
|
||||
if (!mounted) return;
|
||||
if (_dialogVisible) return;
|
||||
final ctx = appRouter.routerDelegate.navigatorKey.currentContext;
|
||||
if (ctx == null) return;
|
||||
|
||||
final tracking = ref.read(sfTrackingProvider);
|
||||
final kind = _kindLabel(result);
|
||||
|
||||
tracking.appUpdateDialogShown(
|
||||
kind: kind,
|
||||
latestBuild: result.latestBuild,
|
||||
currentBuild: result.currentBuild,
|
||||
);
|
||||
|
||||
_dialogVisible = true;
|
||||
showAppUpdateDialog(
|
||||
ctx,
|
||||
result: result,
|
||||
onDismiss: () {
|
||||
if (result is SoftUpdate) {
|
||||
tracking.appUpdateDialogDismissed(latestBuild: result.latestBuild);
|
||||
ref.read(appVersionCheckProvider.notifier).markSoftDismissed(result);
|
||||
}
|
||||
},
|
||||
onUpdateTapped: () => tracking.appUpdateCtaTapped(
|
||||
kind: kind,
|
||||
latestBuild: result.latestBuild,
|
||||
),
|
||||
).whenComplete(() {
|
||||
_dialogVisible = false;
|
||||
});
|
||||
}
|
||||
|
||||
String _kindLabel(AvailableUpdate result) {
|
||||
return switch (result) {
|
||||
SoftUpdate() => 'soft',
|
||||
ForceUpdate() => 'force',
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.listen<AsyncValue<AppVersionCheckResult>>(
|
||||
appVersionCheckProvider,
|
||||
(previous, next) {
|
||||
next.whenData(_onResultEmitted);
|
||||
},
|
||||
);
|
||||
return widget.child;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'app_version_check_result.dart';
|
||||
import 'app_version_check_service.dart';
|
||||
|
||||
export 'app_version_check_result.dart';
|
||||
export 'app_version_check_service.dart';
|
||||
export 'dismissed_build_store.dart';
|
||||
export 'remote_config_reader.dart';
|
||||
|
||||
final appVersionCheckServiceProvider = Provider<AppVersionCheckService>((ref) {
|
||||
return AppVersionCheckService();
|
||||
});
|
||||
|
||||
class AppVersionCheck extends AsyncNotifier<AppVersionCheckResult> {
|
||||
Future<void>? _inflight;
|
||||
|
||||
AppVersionCheckService get _service =>
|
||||
ref.read(appVersionCheckServiceProvider);
|
||||
|
||||
@override
|
||||
Future<AppVersionCheckResult> build() {
|
||||
return _service.check();
|
||||
}
|
||||
|
||||
Future<void> refresh() => _runSerialized(() async {
|
||||
state = AsyncData(await _service.check());
|
||||
});
|
||||
|
||||
Future<void> markSoftDismissed(SoftUpdate result) =>
|
||||
_runSerialized(() async {
|
||||
await _service.markSoftDismissed(result.latestBuild);
|
||||
state = const AsyncData(NoUpdate());
|
||||
});
|
||||
|
||||
Future<void> _runSerialized(Future<void> Function() op) async {
|
||||
final previous = _inflight;
|
||||
final completer = Completer<void>();
|
||||
_inflight = completer.future;
|
||||
try {
|
||||
if (previous != null) {
|
||||
try {
|
||||
await previous;
|
||||
} catch (_) {}
|
||||
}
|
||||
await op();
|
||||
} finally {
|
||||
completer.complete();
|
||||
if (identical(_inflight, completer.future)) {
|
||||
_inflight = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final appVersionCheckProvider =
|
||||
AsyncNotifierProvider<AppVersionCheck, AppVersionCheckResult>(
|
||||
AppVersionCheck.new,
|
||||
);
|
||||
@@ -0,0 +1,39 @@
|
||||
sealed class AppVersionCheckResult {
|
||||
const AppVersionCheckResult();
|
||||
}
|
||||
|
||||
class NoUpdate extends AppVersionCheckResult {
|
||||
const NoUpdate();
|
||||
}
|
||||
|
||||
sealed class AvailableUpdate extends AppVersionCheckResult {
|
||||
const AvailableUpdate({
|
||||
required this.message,
|
||||
required this.storeUrl,
|
||||
required this.latestBuild,
|
||||
required this.currentBuild,
|
||||
});
|
||||
|
||||
final String message;
|
||||
final String storeUrl;
|
||||
final int latestBuild;
|
||||
final int currentBuild;
|
||||
}
|
||||
|
||||
class SoftUpdate extends AvailableUpdate {
|
||||
const SoftUpdate({
|
||||
required super.message,
|
||||
required super.storeUrl,
|
||||
required super.latestBuild,
|
||||
required super.currentBuild,
|
||||
});
|
||||
}
|
||||
|
||||
class ForceUpdate extends AvailableUpdate {
|
||||
const ForceUpdate({
|
||||
required super.message,
|
||||
required super.storeUrl,
|
||||
required super.latestBuild,
|
||||
required super.currentBuild,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import 'app_version_check_result.dart';
|
||||
import 'dismissed_build_store.dart';
|
||||
import 'remote_config_keys.dart';
|
||||
import 'remote_config_reader.dart';
|
||||
|
||||
typedef CurrentBuildLoader = Future<int> Function();
|
||||
|
||||
Future<int> defaultCurrentBuildLoader() async {
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
return int.tryParse(info.buildNumber) ?? 0;
|
||||
}
|
||||
|
||||
class AppVersionCheckService {
|
||||
AppVersionCheckService({
|
||||
RemoteConfigReader? remoteConfig,
|
||||
DismissedBuildStore? dismissedStore,
|
||||
CurrentBuildLoader? currentBuildLoader,
|
||||
bool? isIos,
|
||||
}) : _remoteConfig = remoteConfig ?? FirebaseRemoteConfigReader(),
|
||||
_dismissedStore = dismissedStore ?? SharedPrefsDismissedBuildStore(),
|
||||
_currentBuildLoader = currentBuildLoader ?? defaultCurrentBuildLoader,
|
||||
_isIos = isIos ?? Platform.isIOS;
|
||||
|
||||
final RemoteConfigReader _remoteConfig;
|
||||
final DismissedBuildStore _dismissedStore;
|
||||
final CurrentBuildLoader _currentBuildLoader;
|
||||
final bool _isIos;
|
||||
|
||||
Future<AppVersionCheckResult> check() async {
|
||||
try {
|
||||
final currentBuild = await _currentBuildLoader();
|
||||
|
||||
try {
|
||||
await _remoteConfig.fetchAndActivate();
|
||||
} catch (e) {
|
||||
debugPrint('[AppVersionCheck] RC fetch failed: $e');
|
||||
}
|
||||
|
||||
final minRequired = _remoteConfig.getInt(RemoteConfigKeys.minRequiredBuild);
|
||||
final latest = _remoteConfig.getInt(RemoteConfigKeys.latestBuild);
|
||||
final forceFlag = _remoteConfig.getBool(RemoteConfigKeys.updateForce);
|
||||
final message = _remoteConfig.getString(RemoteConfigKeys.updateMessage);
|
||||
final storeUrl = _isIos
|
||||
? _remoteConfig.getString(RemoteConfigKeys.updateUrlIos)
|
||||
: _remoteConfig.getString(RemoteConfigKeys.updateUrlAndroid);
|
||||
|
||||
if (forceFlag || currentBuild < minRequired) {
|
||||
return ForceUpdate(
|
||||
message: message,
|
||||
storeUrl: storeUrl,
|
||||
latestBuild: latest,
|
||||
currentBuild: currentBuild,
|
||||
);
|
||||
}
|
||||
|
||||
if (currentBuild < latest) {
|
||||
final dismissedFor = await _dismissedStore.read();
|
||||
if (latest <= dismissedFor) {
|
||||
return const NoUpdate();
|
||||
}
|
||||
return SoftUpdate(
|
||||
message: message,
|
||||
storeUrl: storeUrl,
|
||||
latestBuild: latest,
|
||||
currentBuild: currentBuild,
|
||||
);
|
||||
}
|
||||
|
||||
return const NoUpdate();
|
||||
} catch (e) {
|
||||
debugPrint('[AppVersionCheck] check failed: $e');
|
||||
return const NoUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markSoftDismissed(int latestBuild) async {
|
||||
try {
|
||||
await _dismissedStore.write(latestBuild);
|
||||
} catch (e) {
|
||||
debugPrint('[AppVersionCheck] markSoftDismissed failed: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
abstract class DismissedBuildStore {
|
||||
Future<int> read();
|
||||
Future<void> write(int latestBuild);
|
||||
}
|
||||
|
||||
class SharedPrefsDismissedBuildStore implements DismissedBuildStore {
|
||||
static const _key = 'app_update_dismissed_for_latest_build';
|
||||
|
||||
@override
|
||||
Future<int> read() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getInt(_key) ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> write(int latestBuild) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(_key, latestBuild);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
class RemoteConfigKeys {
|
||||
const RemoteConfigKeys._();
|
||||
|
||||
static const minRequiredBuild = 'min_required_build';
|
||||
static const latestBuild = 'latest_build';
|
||||
static const updateForce = 'update_force';
|
||||
static const updateMessage = 'update_message';
|
||||
static const updateUrlIos = 'update_url_ios';
|
||||
static const updateUrlAndroid = 'update_url_android';
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:firebase_remote_config/firebase_remote_config.dart';
|
||||
|
||||
abstract class RemoteConfigReader {
|
||||
Future<void> fetchAndActivate();
|
||||
int getInt(String key);
|
||||
bool getBool(String key);
|
||||
String getString(String key);
|
||||
}
|
||||
|
||||
class FirebaseRemoteConfigReader implements RemoteConfigReader {
|
||||
FirebaseRemoteConfigReader([FirebaseRemoteConfig? rc])
|
||||
: _rc = rc ?? FirebaseRemoteConfig.instance;
|
||||
|
||||
final FirebaseRemoteConfig _rc;
|
||||
|
||||
@override
|
||||
Future<void> fetchAndActivate() => _rc.fetchAndActivate();
|
||||
|
||||
@override
|
||||
int getInt(String key) => _rc.getInt(key);
|
||||
|
||||
@override
|
||||
bool getBool(String key) => _rc.getBool(key);
|
||||
|
||||
@override
|
||||
String getString(String key) => _rc.getString(key);
|
||||
}
|
||||
20
apps/mobile_app/lib/core/config/app_mode.dart
Normal file
20
apps/mobile_app/lib/core/config/app_mode.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
/// Compile-time constant that controls which app the splash screen
|
||||
/// navigates to when the app starts.
|
||||
///
|
||||
/// Set via `--dart-define=APP_MODE=payment` (or `legacy`) at launch time.
|
||||
/// Defaults to `legacy` to preserve historical behavior when no flag is
|
||||
/// passed (e.g. `flutter run` from CLI without arguments).
|
||||
///
|
||||
/// Used only for local development to switch between the legacy app
|
||||
/// (watch/device control) and the payment app (Treezor wallet) without
|
||||
/// needing separate flavors or entry points.
|
||||
const String appMode = String.fromEnvironment(
|
||||
'APP_MODE',
|
||||
defaultValue: 'legacy',
|
||||
);
|
||||
|
||||
/// Whether the app should boot into the payment (Treezor wallet) flow.
|
||||
bool get isPaymentMode => appMode == 'payment';
|
||||
|
||||
/// Whether the app should boot into the legacy (watch/device) flow.
|
||||
bool get isLegacyMode => appMode == 'legacy';
|
||||
73
apps/mobile_app/lib/core/firebase_init.dart
Normal file
73
apps/mobile_app/lib/core/firebase_init.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_performance/firebase_performance.dart';
|
||||
import 'package:firebase_remote_config/firebase_remote_config.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
import '../config/env/environment_enum.dart';
|
||||
import '../firebase_options_dev.dart' as dev_options;
|
||||
import '../firebase_options_prod.dart' as prod_options;
|
||||
import '../firebase_options_staging.dart' as staging_options;
|
||||
import 'app_version_check/remote_config_keys.dart';
|
||||
|
||||
Future<void> setupFirebase(EnvironmentEnum env) async {
|
||||
final FirebaseOptions options;
|
||||
switch (env) {
|
||||
case EnvironmentEnum.development:
|
||||
options = dev_options.DefaultFirebaseOptions.currentPlatform;
|
||||
case EnvironmentEnum.staging:
|
||||
options = staging_options.DefaultFirebaseOptions.currentPlatform;
|
||||
case EnvironmentEnum.production:
|
||||
options = prod_options.DefaultFirebaseOptions.currentPlatform;
|
||||
}
|
||||
|
||||
await Firebase.initializeApp(options: options);
|
||||
|
||||
// Report crashes in ALL builds (debug + release) so we catch issues during testing too.
|
||||
// TODO(gdpr): wire `enabled` to real consent once the fix in backlog lands.
|
||||
final crashlytics = FirebaseCrashlyticsService(enabled: true);
|
||||
FlutterError.onError = (details) =>
|
||||
crashlytics.recordFlutterError(details, fatal: true);
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
crashlytics.recordError(error, stack, fatal: true);
|
||||
return true;
|
||||
};
|
||||
|
||||
await FirebaseAnalytics.instance.setUserProperty(
|
||||
name: 'env',
|
||||
value: env.name,
|
||||
);
|
||||
|
||||
final remoteConfig = FirebaseRemoteConfig.instance;
|
||||
await remoteConfig.setConfigSettings(
|
||||
RemoteConfigSettings(
|
||||
fetchTimeout: const Duration(minutes: 1),
|
||||
minimumFetchInterval: kDebugMode
|
||||
? const Duration(minutes: 1)
|
||||
: const Duration(hours: 12),
|
||||
),
|
||||
);
|
||||
await remoteConfig.setDefaults(<String, Object>{
|
||||
RemoteConfigKeys.minRequiredBuild: 0,
|
||||
RemoteConfigKeys.latestBuild: 0,
|
||||
RemoteConfigKeys.updateForce: false,
|
||||
RemoteConfigKeys.updateMessage: '',
|
||||
RemoteConfigKeys.updateUrlIos: 'https://apps.apple.com/app/id6759875039',
|
||||
RemoteConfigKeys.updateUrlAndroid:
|
||||
'https://play.google.com/store/apps/details?id=com.savefamily.app',
|
||||
BrandLinksKeys.privacyPolicyUrl:
|
||||
'https://savefamilygps.com/pages/politica-de-privacidad-reloj-gps-infantil-localizador-savefamily',
|
||||
BrandLinksKeys.corporateWebsiteUrl: 'https://www.savefamilygps.com/',
|
||||
BrandLinksKeys.helpCenterUrl: 'https://savefamilygpshelp.zendesk.com/hc/es',
|
||||
BrandLinksKeys.supportEmail: 'info@savefamilygps.com',
|
||||
});
|
||||
try {
|
||||
await remoteConfig.fetchAndActivate();
|
||||
} catch (e) {
|
||||
debugPrint('[Firebase] RemoteConfig fetch failed: $e');
|
||||
}
|
||||
|
||||
FirebasePerformance.instance.setPerformanceCollectionEnabled(true);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
class IncomingCallNotificationConfig {
|
||||
const IncomingCallNotificationConfig._();
|
||||
|
||||
static const String channelId = 'sf_incoming_call_channel_v2';
|
||||
static const String legacyChannelId = 'sf_incoming_call_channel';
|
||||
static const String channelName = 'Videollamadas entrantes';
|
||||
static const String channelDescription =
|
||||
'Notificaciones tipo llamada para videollamadas entrantes desde el reloj.';
|
||||
static const int notificationId = 1001;
|
||||
static const String actionAccept = 'accept';
|
||||
static const String actionReject = 'reject';
|
||||
static const String systemRingtoneUri = 'content://settings/system/ringtone';
|
||||
}
|
||||
63
apps/mobile_app/lib/core/incoming_call_strings_cache.dart
Normal file
63
apps/mobile_app/lib/core/incoming_call_strings_cache.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class IncomingCallStrings {
|
||||
const IncomingCallStrings({
|
||||
required this.title,
|
||||
required this.body,
|
||||
required this.acceptLabel,
|
||||
required this.rejectLabel,
|
||||
required this.channelName,
|
||||
required this.channelDescription,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String body;
|
||||
final String acceptLabel;
|
||||
final String rejectLabel;
|
||||
final String channelName;
|
||||
final String channelDescription;
|
||||
}
|
||||
|
||||
class IncomingCallStringsCache {
|
||||
const IncomingCallStringsCache._();
|
||||
|
||||
static const _keyTitle = 'incoming_call.title';
|
||||
static const _keyBody = 'incoming_call.body';
|
||||
static const _keyAccept = 'incoming_call.accept';
|
||||
static const _keyReject = 'incoming_call.reject';
|
||||
static const _keyChannelName = 'incoming_call.channel_name';
|
||||
static const _keyChannelDescription = 'incoming_call.channel_description';
|
||||
|
||||
static const _fallback = IncomingCallStrings(
|
||||
title: 'Videollamada entrante',
|
||||
body: 'El reloj te está llamando',
|
||||
acceptLabel: 'Aceptar',
|
||||
rejectLabel: 'Rechazar',
|
||||
channelName: 'Videollamadas entrantes',
|
||||
channelDescription:
|
||||
'Notificaciones tipo llamada para videollamadas entrantes desde el reloj.',
|
||||
);
|
||||
|
||||
static Future<void> save(IncomingCallStrings strings) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_keyTitle, strings.title);
|
||||
await prefs.setString(_keyBody, strings.body);
|
||||
await prefs.setString(_keyAccept, strings.acceptLabel);
|
||||
await prefs.setString(_keyReject, strings.rejectLabel);
|
||||
await prefs.setString(_keyChannelName, strings.channelName);
|
||||
await prefs.setString(_keyChannelDescription, strings.channelDescription);
|
||||
}
|
||||
|
||||
static Future<IncomingCallStrings> load() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return IncomingCallStrings(
|
||||
title: prefs.getString(_keyTitle) ?? _fallback.title,
|
||||
body: prefs.getString(_keyBody) ?? _fallback.body,
|
||||
acceptLabel: prefs.getString(_keyAccept) ?? _fallback.acceptLabel,
|
||||
rejectLabel: prefs.getString(_keyReject) ?? _fallback.rejectLabel,
|
||||
channelName: prefs.getString(_keyChannelName) ?? _fallback.channelName,
|
||||
channelDescription: prefs.getString(_keyChannelDescription) ??
|
||||
_fallback.channelDescription,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,54 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sca_treezor/sca_treezor.dart';
|
||||
import 'package:sf_app_platform/config/env/environment_enum.dart';
|
||||
import 'package:sf_app_platform/config/env/questia_env_config.dart';
|
||||
import 'package:sf_app_platform/config/env/save_family_env_config.dart';
|
||||
import 'package:sf_app_platform/config/env/save_family_videocall_config.dart';
|
||||
import 'package:sf_app_platform/core/app_provider_container.dart';
|
||||
import 'package:sf_app_platform/core/config/app_mode.dart';
|
||||
import 'package:sf_app_platform/core/firebase_init.dart';
|
||||
import 'package:sf_app_platform/core/notifications_init.dart';
|
||||
import 'package:sf_app_platform/navigation/app_router.dart';
|
||||
import 'package:sf_app_platform/save_family_app.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:timezone/data/latest_all.dart' as tz;
|
||||
import 'package:videocall_sdk/videocall_sdk.dart';
|
||||
|
||||
Future<void> initApp(EnvironmentEnum env) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
await initializeDateFormatting();
|
||||
tz.initializeTimeZones();
|
||||
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
|
||||
navigationModule();
|
||||
scaTreezorModule();
|
||||
configureAppRouter();
|
||||
videocallSdkModule(SaveFamilyVideocallConfig());
|
||||
themePackages();
|
||||
|
||||
// --- Fase 2: Firebase ---
|
||||
// await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
await setupFirebase(env);
|
||||
|
||||
// --- Fase 2: Sentry ---
|
||||
// await initSentry(env);
|
||||
// TODO Fase 2: await initSentry(env);
|
||||
|
||||
configureAppRouter();
|
||||
onRouterReady();
|
||||
|
||||
await configureDependencies(
|
||||
QuestiaEnvConfig(),
|
||||
SaveFamilyEnvConfig(),
|
||||
log: env.isDevelopment || kDebugMode,
|
||||
onTokenExpired: () => appRouter.go(AppRoutes.scaTreezor),
|
||||
onTokenExpired: isPaymentMode
|
||||
? () => appRouter.go(AppRoutes.scaTreezor)
|
||||
: null,
|
||||
onUnauthorized: () async {
|
||||
final currentLocation =
|
||||
appRouter.routerDelegate.currentConfiguration.uri.path;
|
||||
@@ -39,9 +57,25 @@ Future<void> initApp(EnvironmentEnum env) async {
|
||||
await GetIt.I<TreezorWalletConnectionService>().logout();
|
||||
} catch (_) {}
|
||||
await clearSessionData();
|
||||
appRouter.go(AppRoutes.login);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
appRouter.go(isPaymentMode ? AppRoutes.login : AppRoutes.legacyLogin);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
runApp(const ProviderScope(child: SaveFamilyApp()));
|
||||
appProviderContainer = ProviderContainer(
|
||||
overrides: [
|
||||
sharedPreferencesProvider.overrideWithValue(sharedPreferences),
|
||||
],
|
||||
);
|
||||
|
||||
await setupNotifications();
|
||||
initSfTracking();
|
||||
|
||||
runApp(
|
||||
UncontrolledProviderScope(
|
||||
container: appProviderContainer,
|
||||
child: const SaveFamilyApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
409
apps/mobile_app/lib/core/notifications_init.dart
Normal file
409
apps/mobile_app/lib/core/notifications_init.dart
Normal file
@@ -0,0 +1,409 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:chat/chat.dart';
|
||||
import 'package:device_management/device_management.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_app_platform/core/app_provider_container.dart';
|
||||
import 'package:sf_app_platform/core/incoming_call_notification_config.dart';
|
||||
import 'package:sf_app_platform/core/incoming_call_strings_cache.dart';
|
||||
import 'package:sf_app_platform/navigation/app_router.dart';
|
||||
|
||||
// iOS limitation: incoming-call UX requires PushKit + CallKit + a VoIP cert,
|
||||
// which we don't have yet. On iOS the full-screen call UI and ring-while-killed
|
||||
// behaviour will not work — only the standard notification banner.
|
||||
// See TODO(videocall-ios-callkit) for the migration path.
|
||||
//
|
||||
// TODO(push-data-only): backend sends hybrid pushes (notification + data).
|
||||
// In background/killed the SDK auto-shows the `notification` payload using
|
||||
// `sf_default_channel`. For VIDEO_CALL_FROM that produces a duplicate notif
|
||||
// alongside our custom incoming-call notif (ringtone + full-screen). Backend
|
||||
// must drop the `notification` field at minimum for VIDEO_CALL_FROM pushes,
|
||||
// ideally for all commands. When that happens, this handler should construct
|
||||
// title/body locally with i18n for every command (CHAT_MESSAGE, ALERT, etc.)
|
||||
// and call _localNotifications.show(...) directly.
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
debugPrint('[FCM-bg] messageId=${message.messageId}');
|
||||
debugPrint('[FCM-bg] notification=${message.notification?.title} | ${message.notification?.body}');
|
||||
debugPrint('[FCM-bg] data=${message.data}');
|
||||
if (message.data['command'] == 'VIDEO_CALL_FROM') {
|
||||
await _showIncomingCallNotification(message.data);
|
||||
}
|
||||
}
|
||||
|
||||
const String _localChannelId = 'sf_default_channel';
|
||||
const String _localChannelName = 'General';
|
||||
const String _localChannelDescription =
|
||||
'General notifications shown while the app is in the foreground.';
|
||||
|
||||
final FlutterLocalNotificationsPlugin _localNotifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
Map<String, dynamic>? _pendingNotificationData;
|
||||
bool _routerReady = false;
|
||||
ProviderSubscription<VideocallIncomingArgs?>? _incomingProviderSub;
|
||||
|
||||
Future<void> setupNotifications() async {
|
||||
final messaging = FirebaseMessaging.instance;
|
||||
|
||||
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
|
||||
|
||||
await messaging.requestPermission(alert: true, badge: true, sound: true);
|
||||
|
||||
await messaging.setForegroundNotificationPresentationOptions(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
|
||||
await _initLocalNotifications();
|
||||
_subscribeToIncomingProvider();
|
||||
|
||||
FirebaseMessaging.onMessage.listen(_onForegroundMessage);
|
||||
FirebaseMessaging.onMessageOpenedApp.listen(_onMessageOpenedApp);
|
||||
|
||||
final initialMessage = await messaging.getInitialMessage();
|
||||
if (initialMessage != null) {
|
||||
_onMessageOpenedApp(initialMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void onRouterReady() {
|
||||
_routerReady = true;
|
||||
final pending = _pendingNotificationData;
|
||||
if (pending != null) {
|
||||
_pendingNotificationData = null;
|
||||
_handleNotificationNavigation(pending);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initLocalNotifications() async {
|
||||
const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
const iosInit = DarwinInitializationSettings(
|
||||
requestAlertPermission: false,
|
||||
requestBadgePermission: false,
|
||||
requestSoundPermission: false,
|
||||
);
|
||||
const initSettings = InitializationSettings(
|
||||
android: androidInit,
|
||||
iOS: iosInit,
|
||||
);
|
||||
|
||||
await _localNotifications.initialize(
|
||||
initSettings,
|
||||
onDidReceiveNotificationResponse: _onLocalNotificationTapped,
|
||||
);
|
||||
|
||||
const channel = AndroidNotificationChannel(
|
||||
_localChannelId,
|
||||
_localChannelName,
|
||||
description: _localChannelDescription,
|
||||
importance: Importance.high,
|
||||
);
|
||||
final strings = await IncomingCallStringsCache.load();
|
||||
final callChannel = AndroidNotificationChannel(
|
||||
IncomingCallNotificationConfig.channelId,
|
||||
strings.channelName,
|
||||
description: strings.channelDescription,
|
||||
importance: Importance.max,
|
||||
playSound: true,
|
||||
sound: const UriAndroidNotificationSound(
|
||||
IncomingCallNotificationConfig.systemRingtoneUri,
|
||||
),
|
||||
enableVibration: true,
|
||||
enableLights: true,
|
||||
);
|
||||
final androidPlugin = _localNotifications
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin
|
||||
>();
|
||||
await androidPlugin?.deleteNotificationChannel(
|
||||
IncomingCallNotificationConfig.legacyChannelId,
|
||||
);
|
||||
await androidPlugin?.createNotificationChannel(channel);
|
||||
await androidPlugin?.createNotificationChannel(callChannel);
|
||||
}
|
||||
|
||||
void _subscribeToIncomingProvider() {
|
||||
_incomingProviderSub?.close();
|
||||
_incomingProviderSub = appProviderContainer.listen<VideocallIncomingArgs?>(
|
||||
videocallIncomingProvider,
|
||||
(_, next) {
|
||||
if (next == null) {
|
||||
_dismissIncomingCallNotification();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showIncomingCallNotification(Map<String, dynamic> data) async {
|
||||
final roomNumber = data['roomNumber'] as String?;
|
||||
final appAccount = data['appAccount'] as String?;
|
||||
final sessionId = data['sessionId'] as String?;
|
||||
if (roomNumber == null || appAccount == null || sessionId == null) return;
|
||||
|
||||
final strings = await IncomingCallStringsCache.load();
|
||||
final payload = jsonEncode(data);
|
||||
|
||||
await _localNotifications.show(
|
||||
IncomingCallNotificationConfig.notificationId,
|
||||
strings.title,
|
||||
strings.body,
|
||||
NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
IncomingCallNotificationConfig.channelId,
|
||||
strings.channelName,
|
||||
channelDescription: strings.channelDescription,
|
||||
importance: Importance.max,
|
||||
priority: Priority.max,
|
||||
category: AndroidNotificationCategory.call,
|
||||
fullScreenIntent: true,
|
||||
ongoing: true,
|
||||
autoCancel: false,
|
||||
playSound: true,
|
||||
sound: const UriAndroidNotificationSound(
|
||||
IncomingCallNotificationConfig.systemRingtoneUri,
|
||||
),
|
||||
enableVibration: true,
|
||||
visibility: NotificationVisibility.public,
|
||||
actions: <AndroidNotificationAction>[
|
||||
AndroidNotificationAction(
|
||||
IncomingCallNotificationConfig.actionAccept,
|
||||
strings.acceptLabel,
|
||||
showsUserInterface: true,
|
||||
cancelNotification: true,
|
||||
),
|
||||
AndroidNotificationAction(
|
||||
IncomingCallNotificationConfig.actionReject,
|
||||
strings.rejectLabel,
|
||||
showsUserInterface: false,
|
||||
cancelNotification: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
iOS: const DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
interruptionLevel: InterruptionLevel.timeSensitive,
|
||||
),
|
||||
),
|
||||
payload: payload,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _dismissIncomingCallNotification() async {
|
||||
await _localNotifications.cancel(
|
||||
IncomingCallNotificationConfig.notificationId,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(push-data-only): when backend stops sending the `notification` field,
|
||||
// stop reading message.notification.title/body below. Instead, build title/body
|
||||
// from message.data (e.g., data['carrierName']) plus i18n strings — same way
|
||||
// _showIncomingCallNotification already does for VIDEO_CALL_FROM. Then we can
|
||||
// drop the early-return when notification is null.
|
||||
void _onForegroundMessage(RemoteMessage message) {
|
||||
debugPrint('[FCM-fg] messageId=${message.messageId}');
|
||||
debugPrint('[FCM-fg] notification=${message.notification?.title} | ${message.notification?.body}');
|
||||
debugPrint('[FCM-fg] data=${message.data}');
|
||||
if (message.data['command'] == 'VIDEO_CALL_FROM') {
|
||||
_showIncomingCallNotification(message.data);
|
||||
_handleNotificationNavigation(message.data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_shouldSuppressChatNotification(message.data)) {
|
||||
debugPrint('[FCM-fg] chat notification suppressed by active context');
|
||||
return;
|
||||
}
|
||||
|
||||
final notification = message.notification;
|
||||
if (notification == null) return;
|
||||
|
||||
final notificationId = message.messageId?.hashCode ?? 0;
|
||||
|
||||
_localNotifications.show(
|
||||
notificationId,
|
||||
notification.title,
|
||||
notification.body,
|
||||
const NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
_localChannelId,
|
||||
_localChannelName,
|
||||
channelDescription: _localChannelDescription,
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
),
|
||||
),
|
||||
payload: jsonEncode(message.data),
|
||||
);
|
||||
}
|
||||
|
||||
/// Suppresses chat notifications while the user is actively reading the chat
|
||||
/// surface — same UX as WhatsApp/Telegram.
|
||||
///
|
||||
/// - Chat list → suppress (the list updates reactively via the WebSocket).
|
||||
/// - Conversation matching the incoming chatId → suppress (the message arrives
|
||||
/// through the reactive stream).
|
||||
/// - Conversation viewing a different chat → show (cross-chat notification).
|
||||
/// - Outside the chat surface → show.
|
||||
bool _shouldSuppressChatNotification(Map<String, dynamic> data) {
|
||||
if (data['command'] != 'CHAT_MESSAGE') return false;
|
||||
|
||||
final context = appProviderContainer.read(chatContextProvider);
|
||||
if (context is ChatContextOutsideChat) return false;
|
||||
if (context is ChatContextList) return true;
|
||||
|
||||
final deviceIdentificator = data['deviceIdentificator'] as String?;
|
||||
if (deviceIdentificator == null || deviceIdentificator.isEmpty) return false;
|
||||
|
||||
final incomingChatId = appProviderContainer
|
||||
.read(chatDeeplinkServiceProvider)
|
||||
.resolveClientChatId(
|
||||
chatId: data['chatId'] as String?,
|
||||
deviceIdentificator: deviceIdentificator,
|
||||
);
|
||||
if (incomingChatId == null) return false;
|
||||
|
||||
return context is ChatContextConversation && context.chatId == incomingChatId;
|
||||
}
|
||||
|
||||
void _onMessageOpenedApp(RemoteMessage message) {
|
||||
debugPrint('[FCM-tap] messageId=${message.messageId}');
|
||||
debugPrint('[FCM-tap] notification=${message.notification?.title} | ${message.notification?.body}');
|
||||
debugPrint('[FCM-tap] data=${message.data}');
|
||||
_handleNotificationNavigation(message.data);
|
||||
}
|
||||
|
||||
// TODO(videocall-callkit-migration): tap "Accept" hoy abre el app y muestra
|
||||
// IncomingView (doble-tap para entrar a la llamada). WhatsApp/Telegram entran
|
||||
// directo porque usan CallKit/CallStyle nativos. Migración completa a
|
||||
// flutter_callkit_incoming bloqueada por VoIP cert de Apple. Plan y opciones
|
||||
// en docs/videocall-callkit-migration.md.
|
||||
void _onLocalNotificationTapped(NotificationResponse response) {
|
||||
final payload = response.payload;
|
||||
if (payload == null || payload.isEmpty) return;
|
||||
|
||||
Map<String, dynamic> data;
|
||||
try {
|
||||
data = jsonDecode(payload) as Map<String, dynamic>;
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data['command'] == 'VIDEO_CALL_FROM' &&
|
||||
response.actionId == IncomingCallNotificationConfig.actionReject) {
|
||||
_rejectIncomingCallFromNotification(data);
|
||||
return;
|
||||
}
|
||||
|
||||
_handleNotificationNavigation(data);
|
||||
}
|
||||
|
||||
Future<void> _rejectIncomingCallFromNotification(
|
||||
Map<String, dynamic> data,
|
||||
) async {
|
||||
final roomNumber = data['roomNumber'] as String?;
|
||||
final appAccount = data['appAccount'] as String?;
|
||||
if (roomNumber == null || appAccount == null) {
|
||||
appProviderContainer.read(videocallIncomingProvider.notifier).clear();
|
||||
return;
|
||||
}
|
||||
final chatType = data['chatType'] == 'multi'
|
||||
? VideocallChatType.multi
|
||||
: VideocallChatType.single;
|
||||
final deviceId = parseDeviceIdFromRoom(roomNumber);
|
||||
try {
|
||||
await appProviderContainer
|
||||
.read(videocallSignalingRepositoryProvider)
|
||||
.refuseCall(
|
||||
deviceIdentificator: deviceId,
|
||||
chatType: chatType,
|
||||
appAccount: appAccount,
|
||||
roomNumber: roomNumber,
|
||||
);
|
||||
} catch (error) {
|
||||
debugPrint('[Notification] refuseCall from notif failed: $error');
|
||||
}
|
||||
appProviderContainer.read(videocallIncomingProvider.notifier).clear();
|
||||
}
|
||||
|
||||
void _handleNotificationNavigation(Map<String, dynamic> data) {
|
||||
if (!_routerReady) {
|
||||
_pendingNotificationData = data;
|
||||
return;
|
||||
}
|
||||
|
||||
final currentLocation =
|
||||
appRouter.routerDelegate.currentConfiguration.uri.path;
|
||||
if (!currentLocation.startsWith(AppRoutes.legacyDashboard)) return;
|
||||
|
||||
Map<String, dynamic> resolved = data;
|
||||
final pushData = data['pushData'];
|
||||
if (pushData is String && pushData.isNotEmpty) {
|
||||
try {
|
||||
resolved = jsonDecode(pushData) as Map<String, dynamic>;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
final command = resolved['command'] as String? ?? data['command'] as String?;
|
||||
|
||||
switch (command) {
|
||||
case 'ALERT':
|
||||
appRouter.go(AppRoutes.deviceNotifications);
|
||||
case 'CHAT_MESSAGE':
|
||||
_openChatFromIncoming(resolved);
|
||||
case 'VIDEO_CALL_FROM':
|
||||
final chatType = resolved['chatType'] as String?;
|
||||
final appAccount = resolved['appAccount'] as String?;
|
||||
final roomNumber = resolved['roomNumber'] as String?;
|
||||
final sessionId = resolved['sessionId'] as String?;
|
||||
if (roomNumber == null || appAccount == null || sessionId == null) {
|
||||
return;
|
||||
}
|
||||
appProviderContainer.read(videocallIncomingProvider.notifier).set(
|
||||
VideocallIncomingArgs(
|
||||
roomNumber: roomNumber,
|
||||
appAccount: appAccount,
|
||||
sessionId: sessionId,
|
||||
chatType: chatType == 'multi'
|
||||
? VideocallChatType.multi
|
||||
: VideocallChatType.single,
|
||||
),
|
||||
);
|
||||
appRouter.go(AppRoutes.videocall);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _openChatFromIncoming(Map<String, dynamic> data) async {
|
||||
final deviceIdentificator = data['deviceIdentificator'] as String?;
|
||||
if (deviceIdentificator == null || deviceIdentificator.isEmpty) {
|
||||
appRouter.go(AppRoutes.legacyChat);
|
||||
return;
|
||||
}
|
||||
|
||||
final outcome = await appProviderContainer
|
||||
.read(chatDeeplinkServiceProvider)
|
||||
.prepareIncomingChat(
|
||||
chatId: data['chatId'] as String?,
|
||||
deviceIdentificator: deviceIdentificator,
|
||||
);
|
||||
|
||||
if (outcome == null) {
|
||||
appRouter.go(AppRoutes.legacyChat);
|
||||
return;
|
||||
}
|
||||
|
||||
appRouter.go(AppRoutes.legacyChatConversationFor(outcome.chatId));
|
||||
}
|
||||
68
apps/mobile_app/lib/firebase_options_dev.dart
Normal file
68
apps/mobile_app/lib/firebase_options_dev.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options_dev.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAzo8E_L6iUYWmK1BDFpNqRri1df6CqJiY',
|
||||
appId: '1:535646668726:android:c3a09d6c26f0cdf95e6317',
|
||||
messagingSenderId: '535646668726',
|
||||
projectId: 'sf-platform-pre',
|
||||
storageBucket: 'sf-platform-pre.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBeijehJIznndwIUlbMkj6reYT4z-WHGfQ',
|
||||
appId: '1:535646668726:ios:524afa641f61d7cb5e6317',
|
||||
messagingSenderId: '535646668726',
|
||||
projectId: 'sf-platform-pre',
|
||||
storageBucket: 'sf-platform-pre.firebasestorage.app',
|
||||
iosBundleId: 'com.savefamily.app.dev',
|
||||
);
|
||||
}
|
||||
68
apps/mobile_app/lib/firebase_options_prod.dart
Normal file
68
apps/mobile_app/lib/firebase_options_prod.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options_prod.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDkjNdOAK0ype7wgdgiC1BCKV_pP4s_mlA',
|
||||
appId: '1:950566980029:android:75a7c10b6259d09681aad4',
|
||||
messagingSenderId: '950566980029',
|
||||
projectId: 'sf-platform-pro',
|
||||
storageBucket: 'sf-platform-pro.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyC0_d7Z6uVHHKhaf7JHRROaY6g2mvvpOXU',
|
||||
appId: '1:950566980029:ios:987b4f0b9e9b897481aad4',
|
||||
messagingSenderId: '950566980029',
|
||||
projectId: 'sf-platform-pro',
|
||||
storageBucket: 'sf-platform-pro.firebasestorage.app',
|
||||
iosBundleId: 'com.savefamily.app',
|
||||
);
|
||||
}
|
||||
68
apps/mobile_app/lib/firebase_options_staging.dart
Normal file
68
apps/mobile_app/lib/firebase_options_staging.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options_staging.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAzo8E_L6iUYWmK1BDFpNqRri1df6CqJiY',
|
||||
appId: '1:535646668726:android:b87245b807258e3e5e6317',
|
||||
messagingSenderId: '535646668726',
|
||||
projectId: 'sf-platform-pre',
|
||||
storageBucket: 'sf-platform-pre.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBeijehJIznndwIUlbMkj6reYT4z-WHGfQ',
|
||||
appId: '1:535646668726:ios:5172d626d02dfe215e6317',
|
||||
messagingSenderId: '535646668726',
|
||||
projectId: 'sf-platform-pre',
|
||||
storageBucket: 'sf-platform-pre.firebasestorage.app',
|
||||
iosBundleId: 'com.savefamily.app.stag',
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
import 'package:account/account.dart';
|
||||
import 'package:activity/activity.dart';
|
||||
import 'package:auth/auth.dart';
|
||||
import 'package:legacy_auth/legacy_auth.dart';
|
||||
import 'package:customer_service/customer_service.dart';
|
||||
import 'package:dashboard_shell/dashboard_builder.dart';
|
||||
import 'package:device_management/device_management.dart';
|
||||
import 'package:control_panel/control_panel.dart';
|
||||
import 'package:legacy_dashboard_shell/legacy_dashboard_builder.dart';
|
||||
import 'package:chat/chat.dart';
|
||||
import 'package:location/location.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -9,13 +17,56 @@ import 'package:navigation/navigation.dart';
|
||||
import 'package:notifications/notifications.dart';
|
||||
import 'package:payments/payments.dart';
|
||||
import 'package:profile/profile.dart';
|
||||
import 'package:settings/settings.dart';
|
||||
import 'package:sf_app_platform/core/config/app_mode.dart';
|
||||
import 'package:sf_app_platform/widgets/user_identity_listener.dart';
|
||||
import 'package:splash/splash.dart';
|
||||
|
||||
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
final _legacyControlPanelNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'legacyControlPanel');
|
||||
final _legacyDeviceMgmtNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'legacyDeviceMgmt');
|
||||
final _legacyLocationNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'legacyLocation');
|
||||
final _legacyChatNavKey = GlobalKey<NavigatorState>(debugLabel: 'legacyChat');
|
||||
final _legacySettingsNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'legacySettings');
|
||||
|
||||
final _dashboardHomeNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'dashboardHome');
|
||||
final _dashboardActivityNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'dashboardActivity');
|
||||
final _dashboardNotificationsNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'dashboardNotifications');
|
||||
final _dashboardProfileNavKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'dashboardProfile');
|
||||
|
||||
late final GoRouter appRouter;
|
||||
|
||||
/// Maps the splash's session check result to the destination route based
|
||||
/// on the active [appMode]. Set `--dart-define=APP_MODE=payment` (or use
|
||||
/// the `(Payment)` launch configurations) to boot into the payment app.
|
||||
const _legacySplashRouteMap = <InitialRoute, String>{
|
||||
InitialRoute.onboarding: AppRoutes.legacyOnboarding,
|
||||
InitialRoute.login: AppRoutes.legacyLogin,
|
||||
InitialRoute.home: AppRoutes.controlPanel,
|
||||
InitialRoute.deviceSetup: AppRoutes.legacyDeviceSetup,
|
||||
};
|
||||
|
||||
const _paymentSplashRouteMap = <InitialRoute, String>{
|
||||
InitialRoute.onboarding: AppRoutes.onboarding,
|
||||
InitialRoute.login: AppRoutes.login,
|
||||
InitialRoute.home: AppRoutes.dashboardHome,
|
||||
InitialRoute.deviceSetup: AppRoutes.deviceSetup,
|
||||
};
|
||||
|
||||
void configureAppRouter() {
|
||||
final splashRouteMap = isPaymentMode
|
||||
? _paymentSplashRouteMap
|
||||
: _legacySplashRouteMap;
|
||||
|
||||
appRouter = GoRouter(
|
||||
navigatorKey: rootNavigatorKey,
|
||||
initialLocation: AppRoutes.splash,
|
||||
@@ -24,8 +75,288 @@ void configureAppRouter() {
|
||||
GoRoute(
|
||||
path: AppRoutes.splash,
|
||||
name: 'splash',
|
||||
pageBuilder: SplashBuilder().buildPage,
|
||||
pageBuilder: SplashBuilder(routeMap: splashRouteMap).buildPage,
|
||||
),
|
||||
StatefulShellRoute.indexedStack(
|
||||
builder: (context, state, navShell) {
|
||||
return UserIdentityListener(
|
||||
child: LegacyDashboardBuilder().build(context, navShell),
|
||||
);
|
||||
},
|
||||
branches: [
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _legacyControlPanelNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.controlPanel,
|
||||
name: 'control_panel',
|
||||
pageBuilder: const ControlPanelBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'customer_service',
|
||||
name: 'customer_service',
|
||||
pageBuilder: CustomerServiceBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'notifications',
|
||||
name: 'legacy_notifications',
|
||||
pageBuilder: const DeviceNotificationsBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'account_settings',
|
||||
name: 'account_settings',
|
||||
pageBuilder: AccountSettingsBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'personal_data',
|
||||
name: 'personal_data',
|
||||
pageBuilder: PersonalDataBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'change_password',
|
||||
name: 'change_password',
|
||||
pageBuilder: ChangePasswordBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'linked_devices',
|
||||
name: 'linked_devices',
|
||||
pageBuilder: LinkedDevicesBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'app_users',
|
||||
name: 'app_users',
|
||||
pageBuilder: AppUsersBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'delete_account',
|
||||
name: 'delete_account',
|
||||
pageBuilder: DeleteAccountBuilder().buildPage,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _legacyDeviceMgmtNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.deviceManagement,
|
||||
name: 'device_management',
|
||||
pageBuilder: DeviceManagementBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'scheduled_activities',
|
||||
name: 'scheduled_activities',
|
||||
pageBuilder: const ScheduledActivitiesBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'contacts',
|
||||
name: 'contacts',
|
||||
pageBuilder: const ContactsBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'edit/:contactId',
|
||||
name: 'edit_contact',
|
||||
pageBuilder: const EditContactBuilder().buildPage,
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: 'health',
|
||||
name: 'health',
|
||||
pageBuilder: const HealthBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'remote_connection',
|
||||
name: 'remote_connection',
|
||||
pageBuilder: RemoteConnectionBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'locate_device',
|
||||
name: 'locate_device',
|
||||
pageBuilder: LocateDeviceBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'rewards',
|
||||
name: 'rewards',
|
||||
pageBuilder: RewardsBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'activity_meter',
|
||||
name: 'activity_meter',
|
||||
pageBuilder: const ActivityMeterBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'apps_use',
|
||||
name: 'apps_use',
|
||||
pageBuilder: const AppsUseBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'volume_control',
|
||||
name: 'volume_control',
|
||||
pageBuilder: const VolumeControlBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'do_not_disturb',
|
||||
name: 'do_not_disturb',
|
||||
pageBuilder: const DoNotDisturbBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'call_history',
|
||||
name: 'call_history',
|
||||
pageBuilder: const CallHistoryBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'background_image',
|
||||
name: 'background_image',
|
||||
pageBuilder: const BackgroundImageBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'installed_apps',
|
||||
name: 'installed_apps',
|
||||
pageBuilder: const InstalledAppsBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'schedules',
|
||||
name: 'app_usage_schedules',
|
||||
pageBuilder:
|
||||
const AppUsageSchedulesBuilder().buildPage,
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: 'videocall',
|
||||
name: 'videocall',
|
||||
pageBuilder: const VideocallBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'friends',
|
||||
name: 'friends',
|
||||
pageBuilder: const FriendsBuilder().buildPage,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _legacyLocationNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.legacyLocation,
|
||||
name: 'legacy_location',
|
||||
pageBuilder: const LocationBuilder().buildPage,
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _legacyChatNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.legacyChat,
|
||||
name: 'legacy_chat',
|
||||
pageBuilder: const ChatListBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'conversation/:chatId',
|
||||
name: 'legacy_chat_conversation',
|
||||
pageBuilder: const ChatConversationBuilder().buildPage,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _legacySettingsNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.settings,
|
||||
name: 'settings',
|
||||
pageBuilder: SettingsBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'appearance',
|
||||
name: 'appearance',
|
||||
pageBuilder: AppearanceBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'alarm',
|
||||
name: 'alarm',
|
||||
pageBuilder: AlarmBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'remote_management',
|
||||
name: 'remote_management',
|
||||
pageBuilder: RemoteManagementBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'sos_agenda',
|
||||
name: 'sos_agenda',
|
||||
pageBuilder: SosContactsBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'sound',
|
||||
name: 'sound',
|
||||
pageBuilder: SoundBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'sync_clock',
|
||||
name: 'sync_clock',
|
||||
pageBuilder: SyncClockBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'app_store',
|
||||
name: 'app_store',
|
||||
pageBuilder: AppStoreBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'battery',
|
||||
name: 'battery',
|
||||
pageBuilder: BatteryBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'block_phone',
|
||||
name: 'block_phone',
|
||||
pageBuilder: BlockPhoneBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'disable_functions',
|
||||
name: 'disable_functions',
|
||||
pageBuilder: DisableFunctionsBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'language',
|
||||
name: 'language',
|
||||
pageBuilder: LanguageBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'remote_on_off',
|
||||
name: 'remote_on_off',
|
||||
pageBuilder: RemoteOnOffBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'alerts',
|
||||
name: 'alerts',
|
||||
pageBuilder: AlertsBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'timezone',
|
||||
name: 'timezone',
|
||||
pageBuilder: TimezoneBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'wifi_settings',
|
||||
name: 'wifi_settings',
|
||||
pageBuilder: WifiSettingsBuilder().buildPage,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
GoRoute(
|
||||
path: AppRoutes.login,
|
||||
name: 'login',
|
||||
@@ -67,6 +398,41 @@ void configureAppRouter() {
|
||||
name: 'device_setup',
|
||||
pageBuilder: DeviceSetupBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.legacyLogin,
|
||||
name: 'legacy_login',
|
||||
pageBuilder: LegacyLoginBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.legacySignup,
|
||||
name: 'legacy_signup',
|
||||
pageBuilder: LegacySignupBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.legacyOnboarding,
|
||||
name: 'legacy_onboarding',
|
||||
pageBuilder: LegacyOnboardingBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.legacyLinkPhone,
|
||||
name: 'legacy_request_link_phone',
|
||||
pageBuilder: LegacyRequestLinkPhoneBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.legacyPhoneCode,
|
||||
name: 'legacy_verify_link_phone_code',
|
||||
pageBuilder: LegacyVerifyLinkPhoneCodeBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.legacyRecoverPassword,
|
||||
name: 'legacy_recover_password',
|
||||
pageBuilder: LegacyRequestRecoveryBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.legacyDeviceSetup,
|
||||
name: 'legacy_device_setup',
|
||||
pageBuilder: LegacyDeviceSetupBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.hipayWebView,
|
||||
name: 'hipay_webview',
|
||||
@@ -74,10 +440,13 @@ void configureAppRouter() {
|
||||
),
|
||||
StatefulShellRoute.indexedStack(
|
||||
builder: (context, state, navShell) {
|
||||
return DashboardBuilder().build(context, navShell);
|
||||
return UserIdentityListener(
|
||||
child: DashboardBuilder().build(context, navShell),
|
||||
);
|
||||
},
|
||||
branches: [
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _dashboardHomeNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.dashboardHome,
|
||||
@@ -119,6 +488,26 @@ void configureAppRouter() {
|
||||
name: 'home_extract',
|
||||
pageBuilder: const ExtractBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'edit',
|
||||
name: 'home_edit_child_profile',
|
||||
pageBuilder: const EditChildProfileBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'set-pin',
|
||||
name: 'home_set_card_pin',
|
||||
pageBuilder: const SetCardPinBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'change-pin',
|
||||
name: 'home_change_card_pin',
|
||||
pageBuilder: const ChangeCardPinBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'renew-card',
|
||||
name: 'home_renew_card',
|
||||
pageBuilder: const RenewCardBuilder().buildPage,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -126,6 +515,7 @@ void configureAppRouter() {
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _dashboardActivityNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.dashboardActivity,
|
||||
@@ -135,6 +525,7 @@ void configureAppRouter() {
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _dashboardNotificationsNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.dashboardNotifications,
|
||||
@@ -144,6 +535,7 @@ void configureAppRouter() {
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _dashboardProfileNavKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.dashboardProfile,
|
||||
@@ -160,6 +552,11 @@ void configureAppRouter() {
|
||||
name: 'profile_settings',
|
||||
pageBuilder: const ProfileSettingsBuilder().buildPage,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'edit-personal-data',
|
||||
name: 'profile_edit_personal_data',
|
||||
pageBuilder: const EditPersonalDataBuilder().buildPage,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'payment-methods',
|
||||
name: 'profile_payment_methods',
|
||||
|
||||
46
apps/mobile_app/lib/providers/legacy_heartbeat_service.dart
Normal file
46
apps/mobile_app/lib/providers/legacy_heartbeat_service.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart' show WidgetRef;
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
|
||||
class LegacyHeartbeatService {
|
||||
LegacyHeartbeatService({
|
||||
required WidgetRef ref,
|
||||
required void Function() onUnauthorized,
|
||||
}) : _ref = ref,
|
||||
_onUnauthorized = onUnauthorized;
|
||||
|
||||
final WidgetRef _ref;
|
||||
final void Function() _onUnauthorized;
|
||||
Timer? _timer;
|
||||
|
||||
static const _interval = Duration(minutes: 2);
|
||||
|
||||
void start() {
|
||||
if (_timer != null) return;
|
||||
_beat();
|
||||
_timer = Timer.periodic(_interval, (_) => _beat());
|
||||
debugPrint('[LegacyHeartbeat] started');
|
||||
}
|
||||
|
||||
void stop() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
debugPrint('[LegacyHeartbeat] stopped');
|
||||
}
|
||||
|
||||
Future<void> _beat() async {
|
||||
try {
|
||||
await _ref.read(legacyDevicesProvider.notifier).refresh();
|
||||
debugPrint('[LegacyHeartbeat] devices refreshed');
|
||||
} catch (e) {
|
||||
debugPrint('[LegacyHeartbeat] error: $e');
|
||||
if (e is DioException && e.response?.statusCode == 401) {
|
||||
stop();
|
||||
_onUnauthorized();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,27 @@
|
||||
import 'package:auth/auth.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:legacy_theme/legacy_theme.dart';
|
||||
import 'package:sf_app_platform/core/app_version_check/app_update_gate.dart';
|
||||
import 'package:sf_app_platform/core/app_version_check/app_version_check.dart';
|
||||
import 'package:sf_app_platform/core/config/app_mode.dart';
|
||||
import 'package:sf_app_platform/core/incoming_call_strings_cache.dart';
|
||||
import 'package:sf_app_platform/navigation/app_router.dart';
|
||||
import 'package:navigation/navigation.dart';
|
||||
import 'package:sf_app_platform/providers/app_state_provider.dart';
|
||||
import 'package:sf_app_platform/providers/permissions/permissions_provider.dart';
|
||||
import 'package:sf_app_platform/providers/legacy_heartbeat_service.dart';
|
||||
import 'package:sf_app_platform/providers/wallet_heartbeat_service.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:sf_infrastructure/sf_infrastructure.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
import 'package:sf_localizations/sf_localizations.dart';
|
||||
import 'package:utils/utils.dart';
|
||||
import 'package:fonts/fonts.dart';
|
||||
import 'package:videocall_sdk/videocall_sdk.dart';
|
||||
|
||||
class SaveFamilyApp extends ConsumerStatefulWidget {
|
||||
const SaveFamilyApp({super.key});
|
||||
@@ -22,24 +32,82 @@ class SaveFamilyApp extends ConsumerStatefulWidget {
|
||||
|
||||
class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
|
||||
with WidgetsBindingObserver {
|
||||
late final WalletHeartbeatService walletHeartbeat;
|
||||
WalletHeartbeatService? _walletHeartbeat;
|
||||
LegacyHeartbeatService? _legacyHeartbeat;
|
||||
SfRouterListener? _trackingRouterListener;
|
||||
WebSocketService? _webSocket;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
walletHeartbeat = WalletHeartbeatService(
|
||||
repository: ref.read(treezorRepositoryProvider),
|
||||
sessionLocal: SessionLocalDatasourceImpl(),
|
||||
onError: () => appRouter.go(AppRoutes.scaTreezor),
|
||||
|
||||
_trackingRouterListener = SfRouterListener(
|
||||
listenable: appRouter.routerDelegate,
|
||||
currentScreenName: () {
|
||||
final config = appRouter.routerDelegate.currentConfiguration;
|
||||
if (config.matches.isEmpty) return null;
|
||||
return config.last.route.name;
|
||||
},
|
||||
tracking: sfTracking,
|
||||
);
|
||||
onBeforeSessionCleared = walletHeartbeat.stop;
|
||||
walletHeartbeat.start();
|
||||
|
||||
if (isPaymentMode) {
|
||||
_walletHeartbeat = WalletHeartbeatService(
|
||||
repository: ref.read(treezorRepositoryProvider),
|
||||
sessionLocal: SessionLocalDatasourceImpl(),
|
||||
onError: () => appRouter.go(AppRoutes.scaTreezor),
|
||||
);
|
||||
}
|
||||
|
||||
if (isLegacyMode) {
|
||||
_legacyHeartbeat = LegacyHeartbeatService(
|
||||
ref: ref,
|
||||
onUnauthorized: () {
|
||||
clearSessionData();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
appRouter.go(AppRoutes.legacyLogin);
|
||||
});
|
||||
},
|
||||
);
|
||||
_webSocket = GetIt.I<WebSocketService>();
|
||||
appRouter.routerDelegate.addListener(_onRouteChanged);
|
||||
}
|
||||
|
||||
onBeforeSessionCleared = () {
|
||||
_walletHeartbeat?.stop();
|
||||
_legacyHeartbeat?.stop();
|
||||
_webSocket?.disconnect();
|
||||
GetIt.I<VideocallClient>().logout();
|
||||
FirebaseMessaging.instance.deleteToken().catchError((Object e) {
|
||||
debugPrint('[FCM] deleteToken on logout failed: $e');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
void _onRouteChanged() {
|
||||
final heartbeat = _legacyHeartbeat;
|
||||
if (heartbeat == null) return;
|
||||
|
||||
final location = appRouter.routerDelegate.currentConfiguration.uri.path;
|
||||
if (location.startsWith(AppRoutes.legacyDashboard)) {
|
||||
heartbeat.start();
|
||||
_webSocket?.connect();
|
||||
} else {
|
||||
heartbeat.stop();
|
||||
_webSocket?.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
walletHeartbeat.stop();
|
||||
if (isLegacyMode) {
|
||||
appRouter.routerDelegate.removeListener(_onRouteChanged);
|
||||
}
|
||||
_trackingRouterListener?.dispose();
|
||||
_walletHeartbeat?.stop();
|
||||
_legacyHeartbeat?.stop();
|
||||
_webSocket?.disconnect();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
@@ -49,10 +117,16 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
|
||||
debugPrint('State: $state');
|
||||
ref.read(appLifecycleStateProvider.notifier).setState(state);
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
walletHeartbeat.start();
|
||||
_walletHeartbeat?.start();
|
||||
if (isLegacyMode) {
|
||||
_onRouteChanged();
|
||||
}
|
||||
ref.read(permissionsProvider.notifier).checkPermissions();
|
||||
ref.read(appVersionCheckProvider.notifier).refresh();
|
||||
} else if (state == AppLifecycleState.paused) {
|
||||
walletHeartbeat.stop();
|
||||
_walletHeartbeat?.stop();
|
||||
_legacyHeartbeat?.stop();
|
||||
_webSocket?.disconnect();
|
||||
}
|
||||
super.didChangeAppLifecycleState(state);
|
||||
}
|
||||
@@ -60,32 +134,76 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SizeUtils.init(context: context);
|
||||
ref.watch(pushTokenRefreshListenerProvider);
|
||||
|
||||
return MaterialApp.router(
|
||||
title: 'SaveFamily',
|
||||
theme: ThemeData(
|
||||
// Theme wiring:
|
||||
// - Legacy mode: new `legacy_theme` package (Material 3 + light/dark/system).
|
||||
// - Payment mode: unchanged behaviour (seed-based ColorScheme, light only).
|
||||
final ThemeData lightTheme;
|
||||
final ThemeData? darkTheme;
|
||||
final ThemeMode themeMode;
|
||||
if (isLegacyMode) {
|
||||
final legacyThemeState = ref.watch(legacyThemeNotifierProvider);
|
||||
lightTheme = LegacyAppTheme.light;
|
||||
darkTheme = LegacyAppTheme.dark;
|
||||
themeMode = legacyThemeState.themeMode;
|
||||
} else {
|
||||
lightTheme = ThemeData(
|
||||
fontFamily: AppFonts.stolzl,
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF329E95)),
|
||||
),
|
||||
routerConfig: appRouter,
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: [
|
||||
// CountryLocalizations.delegate,
|
||||
SFLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: [for (final lang in supportedLanguages) Locale(lang)],
|
||||
localeResolutionCallback: (locale, supportedLocales) {
|
||||
if (locale == null) return supportedLocales.first;
|
||||
for (var supportedLocale in supportedLocales) {
|
||||
if (supportedLocale.languageCode == locale.languageCode) {
|
||||
return supportedLocale;
|
||||
);
|
||||
darkTheme = null;
|
||||
themeMode = ThemeMode.light;
|
||||
}
|
||||
|
||||
return AppUpdateGate(
|
||||
child: MaterialApp.router(
|
||||
title: 'SaveFamily',
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: themeMode,
|
||||
routerConfig: appRouter,
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: [
|
||||
SFLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: [for (final lang in supportedLanguages) Locale(lang)],
|
||||
localeResolutionCallback: (locale, supportedLocales) {
|
||||
if (locale == null) return supportedLocales.first;
|
||||
for (var supportedLocale in supportedLocales) {
|
||||
if (supportedLocale.languageCode == locale.languageCode) {
|
||||
return supportedLocale;
|
||||
}
|
||||
}
|
||||
}
|
||||
return supportedLocales.first;
|
||||
},
|
||||
return supportedLocales.first;
|
||||
},
|
||||
builder: (context, child) {
|
||||
_cacheIncomingCallStrings(context);
|
||||
return child ?? const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String? _cachedIncomingCallLocale;
|
||||
|
||||
void _cacheIncomingCallStrings(BuildContext context) {
|
||||
final localeCode = context.locale.languageCode;
|
||||
if (_cachedIncomingCallLocale == localeCode) return;
|
||||
_cachedIncomingCallLocale = localeCode;
|
||||
IncomingCallStringsCache.save(
|
||||
IncomingCallStrings(
|
||||
title: context.translate(I18n.videocallIncomingVideo),
|
||||
body: context.translate(I18n.videocallIncomingPushBody),
|
||||
acceptLabel: context.translate(I18n.videocallAccept),
|
||||
rejectLabel: context.translate(I18n.videocallReject),
|
||||
channelName: context.translate(I18n.videocallNotificationChannelName),
|
||||
channelDescription:
|
||||
context.translate(I18n.videocallNotificationChannelDescription),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
28
apps/mobile_app/lib/widgets/user_identity_listener.dart
Normal file
28
apps/mobile_app/lib/widgets/user_identity_listener.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sf_shared/sf_shared.dart';
|
||||
import 'package:sf_tracking/sf_tracking.dart';
|
||||
|
||||
class UserIdentityListener extends ConsumerWidget {
|
||||
final Widget child;
|
||||
|
||||
const UserIdentityListener({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ref.listen<AsyncValue<UserEntity>>(userInfoProvider, (_, next) {
|
||||
next.whenData((user) {
|
||||
UserInfoTrackingListener(ref.read(sfTrackingProvider)).onUserChanged(
|
||||
userId: user.id,
|
||||
role: user.role,
|
||||
language: user.language,
|
||||
createdAtMillis: user.createdAt,
|
||||
hasPhone: user.phone.isNotEmpty,
|
||||
hasApiKey: user.hasApiKey,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
27
apps/mobile_app/linux/flutter/generated_plugin_registrant.cc
Normal file
27
apps/mobile_app/linux/flutter/generated_plugin_registrant.cc
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <record_linux/record_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
|
||||
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) record_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin");
|
||||
record_linux_plugin_register_with_registrar(record_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
15
apps/mobile_app/linux/flutter/generated_plugin_registrant.h
Normal file
15
apps/mobile_app/linux/flutter/generated_plugin_registrant.h
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
27
apps/mobile_app/linux/flutter/generated_plugins.cmake
Normal file
27
apps/mobile_app/linux/flutter/generated_plugins.cmake
Normal file
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
audioplayers_linux
|
||||
file_selector_linux
|
||||
record_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user