From a3d29400bb7ae18594d2c9446f83884769c4da51 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 31 Jan 2024 08:47:31 +0400 Subject: [PATCH 01/37] added gap package dependency --- pubspec.lock | 8 ++++++++ pubspec.yaml | 1 + 2 files changed, 9 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index e1874330..48acd7a1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -525,6 +525,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + gap: + dependency: "direct main" + description: + name: gap + sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d + url: "https://pub.dev" + source: hosted + version: "3.0.1" get_it: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 12a071d1..fe555382 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: flutter_markdown: ^0.6.18+2 flutter_secure_storage: ^9.0.0 flutter_svg: ^2.0.9 + gap: 3.0.1 get_it: ^7.6.4 gql: ^1.0.0 graphql: ^5.1.3 From a516d60f68052f715c88da0da8531227b44b68fa Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Tue, 12 Mar 2024 03:44:04 +0300 Subject: [PATCH 02/37] chore: dependencies bump --- pubspec.lock | 326 ++++++++++++++++++++++++++++----------------------- pubspec.yaml | 44 +++---- 2 files changed, 201 insertions(+), 169 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 9399b1cf..77fa4921 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,34 +5,34 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.4.1" animations: dependency: "direct main" description: name: animations - sha256: ef57563eed3620bd5d75ad96189846aca1e033c0c45fc9a7d26e80ab02b88a70 + sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb url: "https://pub.dev" source: hosted - version: "2.0.8" + version: "2.0.11" archive: dependency: transitive description: name: archive - sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.4.9" + version: "3.4.10" args: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: auto_route - sha256: "82f8df1d177416bc6b7a449127d0270ff1f0f633a91f2ceb7a85d4f07c3affa1" + sha256: "2286341d74c20525e852c43a14062a4042faeb055ea08b82bb6ac107f8250897" url: "https://pub.dev" source: hosted - version: "7.8.4" + version: "7.8.5" auto_route_generator: dependency: "direct dev" description: @@ -77,18 +77,18 @@ packages: dependency: transitive description: name: bloc - sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" + sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e url: "https://pub.dev" source: hosted - version: "8.1.2" + version: "8.1.3" bloc_concurrency: dependency: "direct main" description: name: bloc_concurrency - sha256: d945b33641a3c3f12fa12a1437e77f784444dd9a3a66a3a4f5aaa6e049154d36 + sha256: "5857eb6653b4dd5e30e1ffab91037957fd64a9b9c5e53d26714ef25a46c04679" url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.2.4" boolean_selector: dependency: transitive description: @@ -125,26 +125,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b" + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" url: "https://pub.dev" source: hosted - version: "2.4.7" + version: "2.4.8" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.11" + version: "7.3.0" built_collection: dependency: transitive description: @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: built_value - sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2" + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e url: "https://pub.dev" source: hosted - version: "8.8.0" + version: "8.9.1" characters: dependency: transitive description: @@ -181,10 +181,10 @@ packages: dependency: transitive description: name: cli_util - sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.4.1" clock: dependency: transitive description: @@ -197,10 +197,10 @@ packages: dependency: transitive description: name: code_builder - sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.10.0" collection: dependency: "direct main" description: @@ -237,10 +237,10 @@ packages: dependency: transitive description: name: coverage - sha256: ac86d3abab0f165e4b8f561280ff4e066bceaac83c424dd19f1ae2c2fcd12ca9 + sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" url: "https://pub.dev" source: hosted - version: "1.7.1" + version: "1.7.2" crypt: dependency: "direct main" description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.6" dbus: dependency: transitive description: @@ -285,10 +285,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6" + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" url: "https://pub.dev" source: hosted - version: "9.1.1" + version: "9.1.2" device_info_plus_platform_interface: dependency: transitive description: @@ -301,10 +301,10 @@ packages: dependency: "direct main" description: name: dio - sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" + sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.4.1" duration: dependency: "direct main" description: @@ -317,18 +317,18 @@ packages: dependency: "direct main" description: name: dynamic_color - sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f" + sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d url: "https://pub.dev" source: hosted - version: "1.6.8" + version: "1.7.0" easy_localization: dependency: "direct main" description: name: easy_localization - sha256: de63e3b422adfc97f256cbb3f8cf12739b6a4993d390f3cadb3f51837afaefe5 + sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" easy_logger: dependency: transitive description: @@ -373,10 +373,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" file: dependency: transitive description: @@ -397,10 +397,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "5a74434cc83bf64346efb562f1a06eefaf1bcb530dc3d96a104f631a1eff8d79" + sha256: "00b74ae680df6b1135bdbea00a7d1fc072a9180b7c3f3702e4b19a9943f5ed7d" url: "https://pub.dev" source: hosted - version: "0.65.0" + version: "0.66.2" flutter: dependency: "direct main" description: flutter @@ -410,18 +410,18 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae + sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" url: "https://pub.dev" source: hosted - version: "8.1.3" + version: "8.1.4" flutter_hooks: dependency: transitive description: name: flutter_hooks - sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec" + sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 url: "https://pub.dev" source: hosted - version: "0.20.3" + version: "0.20.5" flutter_launcher_icons: dependency: "direct dev" description: @@ -447,10 +447,10 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: "35108526a233cc0755664d445f8a6b4b61e6f8fe993b3658b80b4a26827fc196" + sha256: cb44f7831b23a6bdd0f501718b0d2e8045cbc625a15f668af37ddb80314821db url: "https://pub.dev" source: hosted - version: "0.6.18+2" + version: "0.6.21" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -511,10 +511,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -561,10 +561,10 @@ packages: dependency: "direct main" description: name: gql - sha256: aa3e0be4548353007b6e6fd24fcad0ce8c1179f9cb2ae5239d392fddb84a5ce5 + sha256: afe032332ddfa69b79f1dea2ad7d95923d4993c1b269b224fc7bb3d17e32d33c url: "https://pub.dev" source: hosted - version: "1.0.1-alpha+1700868214564" + version: "1.0.1-alpha+1709845491443" gql_code_builder: dependency: transitive description: @@ -577,10 +577,10 @@ packages: dependency: transitive description: name: gql_dedupe_link - sha256: e97e3f9490add43ba96cf5cc02d9d10a3723965c0bcc7bb1e04ef4f2e7a31a00 + sha256: "2971173c68623d5c43f5327ea899bd2ee64ce3461c1263f240b4bb6211f667be" url: "https://pub.dev" source: hosted - version: "2.0.4-alpha+1700868214643" + version: "2.0.4-alpha+1709845491527" gql_error_link: dependency: transitive description: @@ -609,10 +609,10 @@ packages: dependency: transitive description: name: gql_link - sha256: "48dbf63b4831d800a2ce9675c9fecea3c9f2801de92072c7644a4bc52aa26c13" + sha256: "177500e250b3742d6d2673d57961e8413b6593dc6bd6a512c51865b6cf096f7e" url: "https://pub.dev" source: hosted - version: "1.0.1-alpha+1700868214578" + version: "1.0.1-alpha+1709845491457" gql_transform_link: dependency: transitive description: @@ -633,18 +633,18 @@ packages: dependency: "direct main" description: name: graphql - sha256: "4ac531068107dffef188c74e7ff662777b729e9d5e0686f71623d4af1e3751c8" + sha256: d066e53446166c12537458386b507f7426f2b8801ebafc184576aab3cbc64d56 url: "https://pub.dev" source: hosted - version: "5.2.0-beta.6" + version: "5.2.0-beta.7" graphql_codegen: - dependency: "direct main" + dependency: "direct dev" description: name: graphql_codegen - sha256: "6702f85cfdf46cd416c3de3a4fcfecc482f15db23058178e4142fb24e604fb0c" + sha256: "943e1d6f04eece8dcdf54c128b53bd4ea62b746d6384ebcbbe15e5adaeaf89ff" url: "https://pub.dev" source: hosted - version: "0.13.9" + version: "0.13.11" graphql_flutter: dependency: "direct main" description: @@ -689,10 +689,10 @@ packages: dependency: "direct main" description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -713,10 +713,10 @@ packages: dependency: transitive description: name: image - sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" + sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" url: "https://pub.dev" source: hosted - version: "4.1.3" + version: "4.1.7" intl: dependency: "direct main" description: @@ -765,6 +765,30 @@ packages: url: "https://pub.dev" source: hosted version: "6.7.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: @@ -777,34 +801,34 @@ packages: dependency: "direct main" description: name: local_auth - sha256: "7e6c63082e399b61e4af71266b012e767a5d4525dd6e9ba41e174fd42d76e115" + sha256: "280421b416b32de31405b0a25c3bd42dfcef2538dfbb20c03019e02a5ed55ed0" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.2.0" local_auth_android: dependency: transitive description: name: local_auth_android - sha256: df4ccb3193525b8a60c78a5ca7bf188a47705bcf77bcc837a6b2cf6da64ae0e2 + sha256: "3bcd732dda7c75fcb7ddaef12e131230f53dcc8c00790d0d6efb3aa0fbbeda57" url: "https://pub.dev" source: hosted - version: "1.0.35" - local_auth_ios: + version: "1.0.37" + local_auth_darwin: dependency: transitive description: - name: local_auth_ios - sha256: "8293faf72ef0ac4710f209edd03916c2d4c1eeab0483bdcf9b2e659c2f7d737b" + name: local_auth_darwin + sha256: "33381a15b0de2279523eca694089393bb146baebdce72a404555d03174ebc1e9" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.2.2" local_auth_platform_interface: dependency: transitive description: name: local_auth_platform_interface - sha256: fc5bd537970a324260fda506cfb61b33ad7426f37a8ea5c461cf612161ebba54 + sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.10" local_auth_windows: dependency: transitive description: @@ -825,50 +849,50 @@ packages: dependency: transitive description: name: markdown - sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd + sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 url: "https://pub.dev" source: hosted - version: "7.1.1" + version: "7.2.2" mask_text_input_formatter: dependency: transitive description: name: mask_text_input_formatter - sha256: "8182cae94ff153e70071f86523d0d09112094e9de366a2e749e6aa9c865e70f2" + sha256: "978c58ec721c25621ceb468e633f4eef64b64d45424ac4540e0565d4f7c800cd" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.9.0" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: "direct main" description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" modal_bottom_sheet: dependency: "direct main" description: @@ -937,10 +961,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_parsing: dependency: transitive description: @@ -953,26 +977,26 @@ packages: dependency: transitive description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -985,10 +1009,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -1009,26 +1033,26 @@ packages: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.7.4" pool: dependency: transitive description: @@ -1049,10 +1073,10 @@ packages: dependency: "direct main" description: name: provider - sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "6.1.2" pub_semver: dependency: "direct main" description: @@ -1105,10 +1129,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: @@ -1121,18 +1145,18 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -1182,10 +1206,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_helper: dependency: transitive description: @@ -1218,6 +1242,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -1310,34 +1342,34 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba + sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.5" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.5" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" url_launcher_macos: dependency: transitive description: @@ -1350,58 +1382,58 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "138bd45b3a456dcfafc46d1a146787424f8d2edfbf2809c9324361e58f851cf7" + sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" uuid: dependency: transitive description: name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "4.3.3" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1430,18 +1462,18 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.4" webkit_inspection_protocol: dependency: transitive description: @@ -1454,10 +1486,10 @@ packages: dependency: transitive description: name: win32 - sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 + sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.3.0" win32_registry: dependency: transitive description: @@ -1470,18 +1502,18 @@ packages: dependency: transitive description: name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" xml: dependency: transitive description: name: xml - sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.4.2" + version: "6.5.0" yaml: dependency: transitive description: @@ -1491,5 +1523,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.1 <4.0.0" - flutter: ">=3.16.1" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 09af8e4b..08345f79 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,52 +5,51 @@ version: 0.10.1+21 environment: sdk: '>=3.2.1 <4.0.0' - flutter: ">=3.16.1" + flutter: ">=3.19.0" dependencies: - animations: ^2.0.8 - auto_route: ^7.8.4 + animations: ^2.0.11 + auto_route: ^7.8.5 auto_size_text: ^3.0.0 - bloc_concurrency: ^0.2.3 + bloc_concurrency: ^0.2.4 crypt: ^4.3.1 collection: ^1.18.0 cubit_form: ^2.0.1 - device_info_plus: ^9.1.1 - dio: ^5.4.0 + device_info_plus: ^9.1.2 + dio: ^5.4.1 duration: ^3.0.13 - dynamic_color: ^1.6.8 - easy_localization: ^3.0.3 + dynamic_color: ^1.7.0 + easy_localization: ^3.0.5 either_option: ^2.0.1-dev.1 equatable: ^2.0.5 - fl_chart: ^0.65.0 + fl_chart: ^0.66.2 flutter: sdk: flutter - flutter_bloc: ^8.1.3 - flutter_markdown: ^0.6.18+2 + flutter_bloc: ^8.1.4 + flutter_markdown: ^0.6.21 flutter_secure_storage: ^9.0.0 - flutter_svg: ^2.0.9 + flutter_svg: ^2.0.10+1 gap: ^3.0.1 - get_it: ^7.6.4 - gql: ^1.0.0 + get_it: ^7.6.7 + gql: ^1.0.0+1 graphql: ^5.1.3 - graphql_codegen: ^0.13.9 graphql_flutter: ^5.1.2 hive: ^2.2.3 hive_flutter: ^1.1.0 - http: ^1.1.2 - intl: ^0.18.0 + http: ^1.2.1 + intl: ^0.18.1 ionicons: ^0.2.2 json_annotation: ^4.8.1 - local_auth: ^2.1.7 - material_color_utilities: ^0.5.0 + local_auth: ^2.2.0 + material_color_utilities: ^0.8.0 modal_bottom_sheet: ^3.0.0-pre nanoid: ^1.0.0 package_info: ^2.0.2 pretty_dio_logger: ^1.3.1 - provider: ^6.1.1 + provider: ^6.1.2 pub_semver: ^2.1.4 timezone: ^0.9.2 - url_launcher: ^6.2.1 + url_launcher: ^6.2.5 # TODO: Developer is not available, update later. # wakelock: ^0.6.2 @@ -58,7 +57,8 @@ dev_dependencies: auto_route_generator: ^7.3.2 flutter_test: sdk: flutter - build_runner: ^2.4.7 + graphql_codegen: ^0.13.11 + build_runner: ^2.4.8 flutter_launcher_icons: ^0.13.1 hive_generator: ^2.0.1 json_serializable: ^6.7.1 From ba2481fbf047da16830211235d8891fbb3ae449b Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Tue, 12 Mar 2024 03:46:03 +0300 Subject: [PATCH 03/37] feat: vscode launch scripts with flavors --- .vscode/launch.json | 53 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..2b83ac77 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,53 @@ +{ + // 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 + "version": "0.2.0", + "configurations": [ + { + "name": "debug", + "request": "launch", + "type": "dart" + }, + { + "name": "profile mode", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "release mode", + "request": "launch", + "type": "dart", + "flutterMode": "release" + }, + { + "name": "debug (fdroid)", + "request": "launch", + "type": "dart", + "args": [ + "--flavor", + "fdroid" + ] + }, + { + "name": "debug (production)", + "request": "launch", + "type": "dart", + "args": [ + "--flavor", + "production" + ] + }, + + { + "name": "debug (nightly)", + "request": "launch", + "type": "dart", + "args": [ + "--flavor", + "nightly" + ] + } + ] +} \ No newline at end of file From 754d1bace2de2f34b865f5acac7fce3821d39a4f Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Tue, 12 Mar 2024 03:48:26 +0300 Subject: [PATCH 04/37] fix: migrated gradle plugin application to new style --- android/app/build.gradle | 60 ++++++++++--------- android/build.gradle | 12 +--- android/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 30 +++++++--- 5 files changed, 56 insertions(+), 50 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 9d678878..a5fc42bb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,10 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,10 +13,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { @@ -21,14 +24,9 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - namespace 'org.selfprivacy.app' - - compileSdkVersion flutter.compileSdkVersion + namespace 'org.selfprivacy.app' + compileSdkVersion flutter.compileSdkVersion ndkVersion flutter.ndkVersion sourceSets { @@ -43,45 +41,50 @@ android { kotlinOptions { jvmTarget = '1.8' } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } lintOptions { disable 'InvalidPackage' } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "org.selfprivacy.app" minSdkVersion 21 - targetSdkVersion 33 - compileSdkVersion 33 + targetSdkVersion 34 + compileSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } + + buildTypes { + debug { - flavorDimensions "default" - productFlavors { - fdroid { - applicationId "pro.kherel.selfprivacy" } - production { - applicationIdSuffix "" + profile { + } - nightly { - applicationIdSuffix ".nightly" - versionCode project.getVersionCode() - versionName "nightly-" + project.getVersionCode() + release { + } } + buildFeatures { + flavorDimensions = ["default"] + } + - flavorDimensions "default" productFlavors { fdroid { + dimension 'default' applicationId "pro.kherel.selfprivacy" } production { - applicationIdSuffix "" + dimension 'default' } nightly { + dimension 'default' applicationIdSuffix ".nightly" versionCode project.getVersionCode() versionName "nightly-" + project.getVersionCode() @@ -93,6 +96,5 @@ flutter { source '../..' } -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} +dependencies {} + diff --git a/android/build.gradle b/android/build.gradle index b1ba7da3..98a94fa2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,5 @@ buildscript { - ext.kotlin_version = '1.9.21' - ext.getVersionCode = { -> + ext.getVersionCode = { -> try { def stdout = new ByteArrayOutputStream() exec { @@ -13,15 +12,6 @@ buildscript { return -1 } } - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } } allprojects { diff --git a/android/gradle.properties b/android/gradle.properties index c396be2a..85faa95d 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.bundle.enableUncompressedNativeLibs=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 02e5f581..aeaff6f8 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bcf..14d2478f 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.9.21" apply false +} + +include ":app" \ No newline at end of file From 551305b55a9817fb362cc0c165dce60f78f97b09 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Sat, 20 Apr 2024 03:11:08 +0400 Subject: [PATCH 05/37] fix: disable automatic scrollbar addition for desktop builds. If view needs a scrollbar, it should be added on all platforms. Framework, by default, adds them only on desktop, so if we add scrollbars in some places (our main builds are still smartphones), on desktop we will get double scrollbars. --- lib/main.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 13b05aad..d11ecd6b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -91,6 +91,9 @@ class SelfprivacyApp extends StatelessWidget { : appSettings.isDarkModeOn ? ThemeMode.dark : ThemeMode.light, + scrollBehavior: const MaterialScrollBehavior().copyWith( + scrollbars: false, + ), builder: (final BuildContext context, final Widget? widget) { Widget error = const Center(child: Text('...rendering error...')); From 32769c9d9fd40d6f35c8f013f43c61c52088306e Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Sat, 20 Apr 2024 03:16:38 +0400 Subject: [PATCH 06/37] fix: selectable new device key. In devices menu, when key for the connection of new device is created, one can select key text for copy. --- lib/ui/pages/devices/new_device.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ui/pages/devices/new_device.dart b/lib/ui/pages/devices/new_device.dart index bbf03728..78b69f04 100644 --- a/lib/ui/pages/devices/new_device.dart +++ b/lib/ui/pages/devices/new_device.dart @@ -39,6 +39,7 @@ class NewDeviceScreen extends StatelessWidget { class _KeyDisplay extends StatelessWidget { const _KeyDisplay({required this.newDeviceKey}); + final String newDeviceKey; @override @@ -47,7 +48,7 @@ class _KeyDisplay extends StatelessWidget { children: [ const Divider(), const SizedBox(height: 16), - Text( + SelectableText( newDeviceKey, style: Theme.of(context).textTheme.bodyLarge!.copyWith( fontSize: 24, From 06513b6fa63cfeeb30d48fba180f8fbd1f42325d Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Sat, 20 Apr 2024 03:19:26 +0400 Subject: [PATCH 07/37] fix: typo in provider constructors. Changed `isAuthotized` to `isAuthorized`. --- lib/logic/providers/dns_providers/cloudflare.dart | 4 ++-- lib/logic/providers/dns_providers/desec.dart | 4 ++-- lib/logic/providers/dns_providers/digital_ocean_dns.dart | 4 ++-- lib/logic/providers/server_providers/digital_ocean.dart | 4 ++-- lib/logic/providers/server_providers/hetzner.dart | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/logic/providers/dns_providers/cloudflare.dart b/lib/logic/providers/dns_providers/cloudflare.dart index e40292a9..eb99db1d 100644 --- a/lib/logic/providers/dns_providers/cloudflare.dart +++ b/lib/logic/providers/dns_providers/cloudflare.dart @@ -27,9 +27,9 @@ class ApiAdapter { class CloudflareDnsProvider extends DnsProvider { CloudflareDnsProvider() : _adapter = ApiAdapter(); CloudflareDnsProvider.load( - final bool isAuthotized, + final bool isAuthorized, ) : _adapter = ApiAdapter( - isWithToken: isAuthotized, + isWithToken: isAuthorized, ); ApiAdapter _adapter; diff --git a/lib/logic/providers/dns_providers/desec.dart b/lib/logic/providers/dns_providers/desec.dart index 188045bf..eaab3aaf 100644 --- a/lib/logic/providers/dns_providers/desec.dart +++ b/lib/logic/providers/dns_providers/desec.dart @@ -22,9 +22,9 @@ class ApiAdapter { class DesecDnsProvider extends DnsProvider { DesecDnsProvider() : _adapter = ApiAdapter(); DesecDnsProvider.load( - final bool isAuthotized, + final bool isAuthorized, ) : _adapter = ApiAdapter( - isWithToken: isAuthotized, + isWithToken: isAuthorized, ); ApiAdapter _adapter; diff --git a/lib/logic/providers/dns_providers/digital_ocean_dns.dart b/lib/logic/providers/dns_providers/digital_ocean_dns.dart index f111c5f3..093123b8 100644 --- a/lib/logic/providers/dns_providers/digital_ocean_dns.dart +++ b/lib/logic/providers/dns_providers/digital_ocean_dns.dart @@ -22,9 +22,9 @@ class ApiAdapter { class DigitalOceanDnsProvider extends DnsProvider { DigitalOceanDnsProvider() : _adapter = ApiAdapter(); DigitalOceanDnsProvider.load( - final bool isAuthotized, + final bool isAuthorized, ) : _adapter = ApiAdapter( - isWithToken: isAuthotized, + isWithToken: isAuthorized, ); ApiAdapter _adapter; diff --git a/lib/logic/providers/server_providers/digital_ocean.dart b/lib/logic/providers/server_providers/digital_ocean.dart index c69dfa8c..207da253 100644 --- a/lib/logic/providers/server_providers/digital_ocean.dart +++ b/lib/logic/providers/server_providers/digital_ocean.dart @@ -38,9 +38,9 @@ class DigitalOceanServerProvider extends ServerProvider { DigitalOceanServerProvider() : _adapter = ApiAdapter(); DigitalOceanServerProvider.load( final String? location, - final bool isAuthotized, + final bool isAuthorized, ) : _adapter = ApiAdapter( - isWithToken: isAuthotized, + isWithToken: isAuthorized, region: location, ); diff --git a/lib/logic/providers/server_providers/hetzner.dart b/lib/logic/providers/server_providers/hetzner.dart index d6e4f1ba..5386cebb 100644 --- a/lib/logic/providers/server_providers/hetzner.dart +++ b/lib/logic/providers/server_providers/hetzner.dart @@ -38,9 +38,9 @@ class HetznerServerProvider extends ServerProvider { HetznerServerProvider() : _adapter = ApiAdapter(); HetznerServerProvider.load( final String? location, - final bool isAuthotized, + final bool isAuthorized, ) : _adapter = ApiAdapter( - isWithToken: isAuthotized, + isWithToken: isAuthorized, region: location, ); From 4f200ae75739e579aea2afe89ec6a8b138ba81e5 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Sat, 20 Apr 2024 13:37:04 +0400 Subject: [PATCH 08/37] fix: typos in field names --- .../server_installation/server_installation_state.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/logic/cubit/server_installation/server_installation_state.dart b/lib/logic/cubit/server_installation/server_installation_state.dart index 3f3ea267..e89e0c6b 100644 --- a/lib/logic/cubit/server_installation/server_installation_state.dart +++ b/lib/logic/cubit/server_installation/server_installation_state.dart @@ -49,9 +49,10 @@ abstract class ServerInstallationState extends Equatable { bool get isPrimaryUserFilled => rootUser != null; bool get isServerCreated => serverDetails != null; - bool get isFullyInitilized => _fulfilementList.every((final el) => el!); + bool get isFullyInitialized => + _fulfillmentList.every((final el) => el == true); ServerSetupProgress get progress => ServerSetupProgress - .values[_fulfilementList.where((final el) => el!).length]; + .values[_fulfillmentList.where((final el) => el!).length]; int get porgressBar { if (progress.index < 6) { @@ -63,7 +64,7 @@ abstract class ServerInstallationState extends Equatable { } } - List get _fulfilementList { + List get _fulfillmentList { final List res = [ isServerProviderApiKeyFilled, isServerTypeFilled, From 22fbbb051ef06f187b0c9166b8d6547ff90d3a70 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Sat, 20 Apr 2024 13:44:14 +0400 Subject: [PATCH 09/37] feat: infobox changed to use wrap. shown as 1 line when content fits, wraps into column when not. --- lib/ui/components/info_box/info_box.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/ui/components/info_box/info_box.dart b/lib/ui/components/info_box/info_box.dart index c2e67def..6b77de9d 100644 --- a/lib/ui/components/info_box/info_box.dart +++ b/lib/ui/components/info_box/info_box.dart @@ -11,15 +11,16 @@ class InfoBox extends StatelessWidget { final bool isWarning; @override - Widget build(final BuildContext context) => Column( - crossAxisAlignment: CrossAxisAlignment.start, + Widget build(final BuildContext context) => Wrap( + spacing: 8.0, + runSpacing: 16.0, + crossAxisAlignment: WrapCrossAlignment.center, children: [ Icon( isWarning ? Icons.warning_amber_outlined : Icons.info_outline, size: 24, color: Theme.of(context).colorScheme.onBackground, ), - const SizedBox(height: 16), Text( text, style: Theme.of(context).textTheme.bodyMedium!.copyWith( From 00545c34b44e706281b8b14604cb71a3161a1fd9 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Sat, 20 Apr 2024 13:53:55 +0400 Subject: [PATCH 10/37] feat: console log feature refactor. listing scroll performance fix, uniform code and widget UI for different log item types, dialog data can now be selected & copy-pasted --- lib/config/get_it_config.dart | 4 +- .../graphql_maps/graphql_api_map.dart | 46 +-- .../api_maps/rest_maps/rest_api_map.dart | 24 +- lib/logic/get_it/console.dart | 17 - lib/logic/get_it/console_model.dart | 16 + lib/logic/models/console_log.dart | 141 ++++++++ lib/logic/models/message.dart | 75 ----- .../components/list_tiles/log_list_tile.dart | 304 ------------------ lib/ui/pages/more/console.dart | 107 ------ .../more/console/console_log_item_dialog.dart | 186 +++++++++++ .../more/console/console_log_item_widget.dart | 71 ++++ lib/ui/pages/more/console/console_page.dart | 138 ++++++++ lib/ui/router/router.dart | 3 +- 13 files changed, 592 insertions(+), 540 deletions(-) delete mode 100644 lib/logic/get_it/console.dart create mode 100644 lib/logic/get_it/console_model.dart create mode 100644 lib/logic/models/console_log.dart delete mode 100644 lib/logic/models/message.dart delete mode 100644 lib/ui/components/list_tiles/log_list_tile.dart delete mode 100644 lib/ui/pages/more/console.dart create mode 100644 lib/ui/pages/more/console/console_log_item_dialog.dart create mode 100644 lib/ui/pages/more/console/console_log_item_widget.dart create mode 100644 lib/ui/pages/more/console/console_page.dart diff --git a/lib/config/get_it_config.dart b/lib/config/get_it_config.dart index 78e40261..aa3db90b 100644 --- a/lib/config/get_it_config.dart +++ b/lib/config/get_it_config.dart @@ -1,12 +1,12 @@ import 'package:get_it/get_it.dart'; import 'package:selfprivacy/logic/get_it/api_config.dart'; import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; -import 'package:selfprivacy/logic/get_it/console.dart'; +import 'package:selfprivacy/logic/get_it/console_model.dart'; import 'package:selfprivacy/logic/get_it/navigation.dart'; export 'package:selfprivacy/logic/get_it/api_config.dart'; export 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; -export 'package:selfprivacy/logic/get_it/console.dart'; +export 'package:selfprivacy/logic/get_it/console_model.dart'; export 'package:selfprivacy/logic/get_it/navigation.dart'; final GetIt getIt = GetIt.instance; diff --git a/lib/logic/api_maps/graphql_maps/graphql_api_map.dart b/lib/logic/api_maps/graphql_maps/graphql_api_map.dart index 6a00f5b6..698f59e8 100644 --- a/lib/logic/api_maps/graphql_maps/graphql_api_map.dart +++ b/lib/logic/api_maps/graphql_maps/graphql_api_map.dart @@ -4,15 +4,10 @@ import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:http/io_client.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart'; -import 'package:selfprivacy/logic/models/message.dart'; +import 'package:selfprivacy/logic/models/console_log.dart'; -void _logToAppConsole(final T objectToLog) { - getIt.get().addMessage( - Message( - text: objectToLog.toString(), - ), - ); -} +void _addConsoleLog(final ConsoleLog message) => + getIt.get().log(message); class RequestLoggingLink extends Link { @override @@ -20,13 +15,13 @@ class RequestLoggingLink extends Link { final Request request, [ final NextLink? forward, ]) async* { - getIt.get().addMessage( - GraphQlRequestMessage( - operation: request.operation, - variables: request.variables, - context: request.context, - ), - ); + _addConsoleLog( + GraphQlRequestConsoleLog( + operation: request.operation, + variables: request.variables, + context: request.context, + ), + ); yield* forward!(request); } } @@ -35,20 +30,25 @@ class ResponseLoggingParser extends ResponseParser { @override Response parseResponse(final Map body) { final response = super.parseResponse(body); - getIt.get().addMessage( - GraphQlResponseMessage( - data: response.data, - errors: response.errors, - context: response.context, - ), - ); + _addConsoleLog( + GraphQlResponseConsoleLog( + data: response.data, + errors: response.errors, + context: response.context, + ), + ); return response; } @override GraphQLError parseError(final Map error) { final graphQlError = super.parseError(error); - _logToAppConsole(graphQlError); + _addConsoleLog( + ManualConsoleLog.warning( + customTitle: 'GraphQL Error', + content: graphQlError.toString(), + ), + ); return graphQlError; } } diff --git a/lib/logic/api_maps/rest_maps/rest_api_map.dart b/lib/logic/api_maps/rest_maps/rest_api_map.dart index 3a8d0571..338b99c2 100644 --- a/lib/logic/api_maps/rest_maps/rest_api_map.dart +++ b/lib/logic/api_maps/rest_maps/rest_api_map.dart @@ -6,7 +6,7 @@ import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/models/message.dart'; +import 'package:selfprivacy/logic/models/console_log.dart'; abstract class RestApiMap { Future getClient({final BaseOptions? customOptions}) async { @@ -57,8 +57,8 @@ abstract class RestApiMap { } class ConsoleInterceptor extends InterceptorsWrapper { - void addMessage(final Message message) { - getIt.get().addMessage(message); + void addConsoleLog(final ConsoleLog message) { + getIt.get().log(message); } @override @@ -66,8 +66,8 @@ class ConsoleInterceptor extends InterceptorsWrapper { final RequestOptions options, final RequestInterceptorHandler handler, ) async { - addMessage( - RestApiRequestMessage( + addConsoleLog( + RestApiRequestConsoleLog( method: options.method, data: options.data.toString(), headers: options.headers, @@ -82,8 +82,8 @@ class ConsoleInterceptor extends InterceptorsWrapper { final Response response, final ResponseInterceptorHandler handler, ) async { - addMessage( - RestApiResponseMessage( + addConsoleLog( + RestApiResponseConsoleLog( method: response.requestOptions.method, statusCode: response.statusCode, data: response.data.toString(), @@ -103,10 +103,12 @@ class ConsoleInterceptor extends InterceptorsWrapper { ) async { final Response? response = err.response; log(err.toString()); - addMessage( - Message.warn( - text: - 'response-uri: ${response?.realUri}\ncode: ${response?.statusCode}\ndata: ${response?.toString()}\n', + addConsoleLog( + ManualConsoleLog.warning( + customTitle: 'RestAPI error', + content: 'response-uri: ${response?.realUri}\n' + 'code: ${response?.statusCode}\n' + 'data: ${response?.toString()}\n', ), ); return super.onError(err, handler); diff --git a/lib/logic/get_it/console.dart b/lib/logic/get_it/console.dart deleted file mode 100644 index a523c5e8..00000000 --- a/lib/logic/get_it/console.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/models/message.dart'; - -class ConsoleModel extends ChangeNotifier { - final List _messages = []; - - List get messages => _messages; - - void addMessage(final Message message) { - messages.add(message); - notifyListeners(); - // Make sure we don't have too many messages - if (messages.length > 500) { - messages.removeAt(0); - } - } -} diff --git a/lib/logic/get_it/console_model.dart b/lib/logic/get_it/console_model.dart new file mode 100644 index 00000000..9ef0d67e --- /dev/null +++ b/lib/logic/get_it/console_model.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/models/console_log.dart'; + +class ConsoleModel extends ChangeNotifier { + final List _logs = []; + List get logs => _logs; + + void log(final ConsoleLog newLog) { + logs.add(newLog); + notifyListeners(); + // Make sure we don't have too many + if (logs.length > 500) { + logs.removeAt(0); + } + } +} diff --git a/lib/logic/models/console_log.dart b/lib/logic/models/console_log.dart new file mode 100644 index 00000000..80fb6240 --- /dev/null +++ b/lib/logic/models/console_log.dart @@ -0,0 +1,141 @@ +import 'package:gql/language.dart'; +import 'package:graphql/client.dart'; +import 'package:intl/intl.dart'; + +enum ConsoleLogSeverity { + normal, + warning, +} + +/// Base entity for console logs. +/// +/// TODO(misterfourtytwo): should we add? +/// +/// * equality override +/// * translations of theese strings +sealed class ConsoleLog { + ConsoleLog({ + final String? customTitle, + this.severity = ConsoleLogSeverity.normal, + }) : title = customTitle ?? + (severity == ConsoleLogSeverity.warning ? 'Error' : 'Log'), + time = DateTime.now(); + + final DateTime time; + final ConsoleLogSeverity severity; + bool get isError => severity == ConsoleLogSeverity.warning; + + /// title for both in listing and in dialog + final String title; + + /// formatted data to be shown in listing + String get content; + + /// data available for copy in dialog + String? get shareableData => '$title\n' + '$content'; + + static final DateFormat _formatter = DateFormat('hh:mm:ss'); + String get timeString => _formatter.format(time); +} + +class ManualConsoleLog extends ConsoleLog { + ManualConsoleLog({ + required this.content, + super.customTitle, + super.severity, + }); + + ManualConsoleLog.warning({ + required this.content, + super.customTitle, + }) : super(severity: ConsoleLogSeverity.warning); + + @override + String content; +} + +class RestApiRequestConsoleLog extends ConsoleLog { + RestApiRequestConsoleLog({ + this.method, + this.uri, + this.headers, + this.data, + super.severity, + }); + + final String? method; + final Uri? uri; + final Map? headers; + final String? data; + + @override + String get title => 'Rest API Request'; + @override + String get content => 'method: $method\n' + 'uri: $uri'; +} + +class RestApiResponseConsoleLog extends ConsoleLog { + RestApiResponseConsoleLog({ + this.method, + this.uri, + this.statusCode, + this.data, + super.severity, + }); + + final String? method; + final Uri? uri; + final int? statusCode; + final String? data; + + @override + String get title => 'Rest API Response'; + @override + String get content => 'method: $method | status code: $statusCode\n' + 'uri: $uri'; +} + +class GraphQlRequestConsoleLog extends ConsoleLog { + GraphQlRequestConsoleLog({ + this.operation, + this.variables, + this.context, + super.severity, + }); + + final Context? context; + final Operation? operation; + final Map? variables; + + @override + String get title => 'GraphQL Request'; + @override + String get content => 'name: ${operation?.operationName}\n' + 'document: ${operation?.document != null ? printNode(operation!.document) : null}'; + String get stringifiedOperation => operation == null + ? 'null' + : 'Operation{\n' + '\tname: ${operation?.operationName},\n' + '\tdocument: ${operation?.document != null ? printNode(operation!.document) : null}\n' + '}'; +} + +class GraphQlResponseConsoleLog extends ConsoleLog { + GraphQlResponseConsoleLog({ + this.data, + this.errors, + this.context, + super.severity, + }); + + final Context? context; + final Map? data; + final List? errors; + + @override + String get title => 'GraphQL Response'; + @override + String get content => 'data: $data'; +} diff --git a/lib/logic/models/message.dart b/lib/logic/models/message.dart deleted file mode 100644 index b722d464..00000000 --- a/lib/logic/models/message.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:graphql/client.dart'; -import 'package:intl/intl.dart'; - -/// TODO(misterfourtytwo): add equality override -class Message { - Message({this.text, this.severity = MessageSeverity.normal}) - : time = DateTime.now(); - Message.warn({this.text}) - : severity = MessageSeverity.warning, - time = DateTime.now(); - - final String? text; - final DateTime time; - final MessageSeverity severity; - - static final DateFormat _formatter = DateFormat('hh:mm'); - String get timeString => _formatter.format(time); -} - -enum MessageSeverity { - normal, - warning, -} - -class RestApiRequestMessage extends Message { - RestApiRequestMessage({ - this.method, - this.uri, - this.data, - this.headers, - }) : super(text: 'request-uri: $uri\nheaders: $headers\ndata: $data'); - - final String? method; - final Uri? uri; - final String? data; - final Map? headers; -} - -class RestApiResponseMessage extends Message { - RestApiResponseMessage({ - this.method, - this.uri, - this.statusCode, - this.data, - }) : super(text: 'response-uri: $uri\ncode: $statusCode\ndata: $data'); - - final String? method; - final Uri? uri; - final int? statusCode; - final String? data; -} - -class GraphQlResponseMessage extends Message { - GraphQlResponseMessage({ - this.data, - this.errors, - this.context, - }) : super(text: 'GraphQL Response\ndata: $data'); - - final Map? data; - final List? errors; - final Context? context; -} - -class GraphQlRequestMessage extends Message { - GraphQlRequestMessage({ - this.operation, - this.variables, - this.context, - }) : super(text: 'GraphQL Request\noperation: $operation'); - - final Operation? operation; - final Map? variables; - final Context? context; -} diff --git a/lib/ui/components/list_tiles/log_list_tile.dart b/lib/ui/components/list_tiles/log_list_tile.dart deleted file mode 100644 index e83765e9..00000000 --- a/lib/ui/components/list_tiles/log_list_tile.dart +++ /dev/null @@ -1,304 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/models/message.dart'; -import 'package:selfprivacy/utils/platform_adapter.dart'; - -class LogListItem extends StatelessWidget { - const LogListItem({ - required this.message, - super.key, - }); - - final Message message; - - @override - Widget build(final BuildContext context) { - final messageItem = message; - if (messageItem is RestApiRequestMessage) { - return _RestApiRequestMessageItem(message: messageItem); - } else if (messageItem is RestApiResponseMessage) { - return _RestApiResponseMessageItem(message: messageItem); - } else if (messageItem is GraphQlResponseMessage) { - return _GraphQlResponseMessageItem(message: messageItem); - } else if (messageItem is GraphQlRequestMessage) { - return _GraphQlRequestMessageItem(message: messageItem); - } else { - return _DefaultMessageItem(message: messageItem); - } - } -} - -class _RestApiRequestMessageItem extends StatelessWidget { - const _RestApiRequestMessageItem({required this.message}); - - final RestApiRequestMessage message; - - @override - Widget build(final BuildContext context) => ListTile( - title: Text( - '${message.method}\n${message.uri}', - ), - subtitle: Text(message.timeString), - leading: const Icon(Icons.upload_outlined), - iconColor: Theme.of(context).colorScheme.secondary, - onTap: () => showDialog( - context: context, - builder: (final BuildContext context) => AlertDialog( - scrollable: true, - title: Text( - '${message.method}\n${message.uri}', - ), - content: Column( - children: [ - Text(message.timeString), - const SizedBox(height: 16), - // Headers is a map of key-value pairs - if (message.headers != null) const Text('Headers'), - if (message.headers != null) - Text( - message.headers!.entries - .map((final entry) => '${entry.key}: ${entry.value}') - .join('\n'), - ), - if (message.data != null && message.data != 'null') - const Text('Data'), - if (message.data != null && message.data != 'null') - Text(message.data!), - ], - ), - actions: [ - // A button to copy the request to the clipboard - if (message.text != null) - TextButton( - onPressed: () { - PlatformAdapter.setClipboard(message.text ?? ''); - }, - child: Text('console_page.copy'.tr()), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('basis.close'.tr()), - ), - ], - ), - ), - ); -} - -class _RestApiResponseMessageItem extends StatelessWidget { - const _RestApiResponseMessageItem({required this.message}); - - final RestApiResponseMessage message; - - @override - Widget build(final BuildContext context) => ListTile( - title: Text( - '${message.statusCode} ${message.method}\n${message.uri}', - ), - subtitle: Text(message.timeString), - leading: const Icon(Icons.download_outlined), - iconColor: Theme.of(context).colorScheme.primary, - onTap: () => showDialog( - context: context, - builder: (final BuildContext context) => AlertDialog( - scrollable: true, - title: Text( - '${message.statusCode} ${message.method}\n${message.uri}', - ), - content: Column( - children: [ - Text(message.timeString), - const SizedBox(height: 16), - // Headers is a map of key-value pairs - if (message.data != null && message.data != 'null') - const Text('Data'), - if (message.data != null && message.data != 'null') - Text(message.data!), - ], - ), - actions: [ - // A button to copy the request to the clipboard - if (message.text != null) - TextButton( - onPressed: () { - PlatformAdapter.setClipboard(message.text ?? ''); - }, - child: Text('console_page.copy'.tr()), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('basis.close'.tr()), - ), - ], - ), - ), - ); -} - -class _GraphQlResponseMessageItem extends StatelessWidget { - const _GraphQlResponseMessageItem({required this.message}); - - final GraphQlResponseMessage message; - - @override - Widget build(final BuildContext context) => ListTile( - title: Text( - 'GraphQL Response at ${message.timeString}', - ), - subtitle: Text( - message.data.toString(), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - leading: const Icon(Icons.arrow_circle_down_outlined), - iconColor: Theme.of(context).colorScheme.tertiary, - onTap: () => showDialog( - context: context, - builder: (final BuildContext context) => AlertDialog( - scrollable: true, - title: Text( - 'GraphQL Response at ${message.timeString}', - ), - content: Column( - children: [ - Text(message.timeString), - const Divider(), - if (message.data != null) const Text('Data'), - // Data is a map of key-value pairs - if (message.data != null) - Text( - message.data!.entries - .map((final entry) => '${entry.key}: ${entry.value}') - .join('\n'), - ), - const Divider(), - if (message.errors != null) const Text('Errors'), - if (message.errors != null) - Text( - message.errors! - .map( - (final entry) => - '${entry.message} at ${entry.locations}', - ) - .join('\n'), - ), - const Divider(), - if (message.context != null) const Text('Context'), - if (message.context != null) - Text( - message.context!.toString(), - ), - ], - ), - actions: [ - // A button to copy the request to the clipboard - if (message.text != null) - TextButton( - onPressed: () { - PlatformAdapter.setClipboard(message.text ?? ''); - }, - child: Text('console_page.copy'.tr()), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('basis.close'.tr()), - ), - ], - ), - ), - ); -} - -class _GraphQlRequestMessageItem extends StatelessWidget { - const _GraphQlRequestMessageItem({required this.message}); - - final GraphQlRequestMessage message; - - @override - Widget build(final BuildContext context) => ListTile( - title: Text( - 'GraphQL Request at ${message.timeString}', - ), - subtitle: Text( - message.operation.toString(), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - leading: const Icon(Icons.arrow_circle_up_outlined), - iconColor: Theme.of(context).colorScheme.secondary, - onTap: () => showDialog( - context: context, - builder: (final BuildContext context) => AlertDialog( - scrollable: true, - title: Text( - 'GraphQL Response at ${message.timeString}', - ), - content: Column( - children: [ - Text(message.timeString), - const Divider(), - if (message.operation != null) const Text('Operation'), - // Data is a map of key-value pairs - if (message.operation != null) - Text( - message.operation!.toString(), - ), - const Divider(), - if (message.variables != null) const Text('Variables'), - if (message.variables != null) - Text( - message.variables!.entries - .map((final entry) => '${entry.key}: ${entry.value}') - .join('\n'), - ), - const Divider(), - if (message.context != null) const Text('Context'), - if (message.context != null) - Text( - message.context!.toString(), - ), - ], - ), - actions: [ - // A button to copy the request to the clipboard - if (message.text != null) - TextButton( - onPressed: () { - PlatformAdapter.setClipboard(message.text ?? ''); - }, - child: Text('console_page.copy'.tr()), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('basis.close'.tr()), - ), - ], - ), - ), - ); -} - -class _DefaultMessageItem extends StatelessWidget { - const _DefaultMessageItem({required this.message}); - - final Message message; - - @override - Widget build(final BuildContext context) => Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: RichText( - text: TextSpan( - style: DefaultTextStyle.of(context).style, - children: [ - TextSpan( - text: '${message.timeString}: \n', - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - TextSpan(text: message.text), - ], - ), - ), - ); -} diff --git a/lib/ui/pages/more/console.dart b/lib/ui/pages/more/console.dart deleted file mode 100644 index 0c60cf81..00000000 --- a/lib/ui/pages/more/console.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/models/message.dart'; -import 'package:selfprivacy/ui/components/list_tiles/log_list_tile.dart'; - -@RoutePage() -class ConsolePage extends StatefulWidget { - const ConsolePage({super.key}); - - @override - State createState() => _ConsolePageState(); -} - -class _ConsolePageState extends State { - bool paused = false; - - @override - void initState() { - super.initState(); - - getIt().addListener(update); - } - - @override - void dispose() { - getIt().removeListener(update); - - super.dispose(); - } - - void update() { - /// listener update could come at any time, like when widget is already - /// unmounted or during frame build, adding as postframe callback ensures - /// that element is marked for rebuild - WidgetsBinding.instance.addPostFrameCallback((final _) { - if (!paused && mounted) { - setState(() => {}); - } - }); - } - - void togglePause() { - paused ^= true; - setState(() {}); - } - - @override - Widget build(final BuildContext context) => SafeArea( - child: Scaffold( - appBar: AppBar( - title: Text('console_page.title'.tr()), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.of(context).pop(), - ), - actions: [ - IconButton( - icon: Icon( - paused ? Icons.play_arrow_outlined : Icons.pause_outlined, - ), - onPressed: togglePause, - ), - ], - ), - body: FutureBuilder( - future: getIt.allReady(), - builder: ( - final BuildContext context, - final AsyncSnapshot snapshot, - ) { - if (snapshot.hasData) { - final List messages = - getIt.get().messages; - - return ListView( - reverse: true, - shrinkWrap: true, - children: [ - const Gap(20), - ...messages.reversed.map( - (final message) => LogListItem( - key: ValueKey(message), - message: message, - ), - ), - ], - ); - } else { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text('console_page.waiting'.tr()), - const Gap(16), - const CircularProgressIndicator.adaptive(), - ], - ); - } - }, - ), - ), - ); -} diff --git a/lib/ui/pages/more/console/console_log_item_dialog.dart b/lib/ui/pages/more/console/console_log_item_dialog.dart new file mode 100644 index 00000000..89e7abb2 --- /dev/null +++ b/lib/ui/pages/more/console/console_log_item_dialog.dart @@ -0,0 +1,186 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/models/console_log.dart'; +import 'package:selfprivacy/utils/platform_adapter.dart'; + +extension on ConsoleLog { + List unwrapContent(final BuildContext context) => switch (this) { + (final RestApiRequestConsoleLog log) => [ + if (log.method != null) _KeyValueRow('method', log.method), + if (log.uri != null) _KeyValueRow('uri', log.uri.toString()), + + // headers bloc + if (log.headers?.isNotEmpty ?? false) const _SectionRow('Headers'), + ...?log.headers?.entries + .map((final entry) => _KeyValueRow(entry.key, entry.value)), + + // data bloc + const _SectionRow('Request data'), + _DataRow(log.data?.toString()), + ], + (final RestApiResponseConsoleLog log) => [ + if (log.method != null) _KeyValueRow('method', log.method), + if (log.uri != null) _KeyValueRow('uri', log.uri.toString()), + if (log.statusCode != null) + _KeyValueRow( + 'statusCode', + log.statusCode.toString(), + ), + + // data bloc + const _SectionRow('Response data'), + _DataRow(log.data?.toString()), + ], + (final GraphQlRequestConsoleLog log) => [ + // context + const _SectionRow('Context'), + _DataRow(log.context?.toString()), + // data + if (log.operation != null) const _SectionRow('Operation'), + _DataRow(log.stringifiedOperation), // errors + if (log.variables?.isNotEmpty ?? false) + const _SectionRow('Variables'), + ...?log.variables?.entries.map( + (final entry) => _KeyValueRow( + entry.key, + '${entry.value}', + ), + ), + ], + (final GraphQlResponseConsoleLog log) => [ + // context + const _SectionRow('Context'), + _DataRow(log.context?.toString()), + // data + if (log.data != null) const _SectionRow('Data'), + ...?log.data?.entries.map( + (final entry) => _KeyValueRow( + entry.key, + '${entry.value}', + ), + ), + // errors + if (log.errors?.isNotEmpty ?? false) const _SectionRow('Errors'), + ...?log.errors?.map( + (final entry) => _KeyValueRow( + entry.message, + '${entry.locations}', + ), + ), + ], + (final ManualConsoleLog log) => [ + _DataRow(log.content), + ], + }; +} + +class ConsoleItemDialog extends StatelessWidget { + const ConsoleItemDialog({ + required this.log, + super.key, + }); + + final ConsoleLog log; + + @override + Widget build(final BuildContext context) { + final content = log.unwrapContent(context); + + return AlertDialog( + scrollable: true, + title: Text(log.title), + content: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + /// TODO(misterfourtytwo): maybe should add translations later + const Text('logged at: '), + SelectableText( + log.timeString, + style: const TextStyle( + fontWeight: FontWeight.w700, + fontFeatures: [FontFeature.tabularFigures()], + ), + ), + ], + ), + const Divider(), + ...content, + ], + ), + actions: [ + // A button to copy the request to the clipboard + if (log.shareableData?.isNotEmpty ?? false) + TextButton( + onPressed: () => PlatformAdapter.setClipboard(log.content), + child: Text('console_page.copy'.tr()), + ), + // close dialog + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('basis.close'.tr()), + ), + ], + ); + } +} + +class _KeyValueRow extends StatelessWidget { + const _KeyValueRow(this.title, this.value); + + final String title; + final String? value; + + @override + Widget build(final BuildContext context) => SelectableText.rich( + TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: '$title: ', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: value ?? ''), + ], + ), + ); +} + +class _DataRow extends StatelessWidget { + const _DataRow(this.data); + final String? data; + + @override + Widget build(final BuildContext context) => SelectableText( + data ?? 'null', + style: const TextStyle(fontWeight: FontWeight.w400), + ); +} + +class _SectionRow extends StatelessWidget { + const _SectionRow(this.title); + final String title; + + @override + Widget build(final BuildContext context) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: DecoratedBox( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + width: 2.4, + ), + ), + ), + child: SelectableText( + title, + style: const TextStyle( + fontWeight: FontWeight.w800, + fontSize: 20, + ), + ), + ), + ); +} diff --git a/lib/ui/pages/more/console/console_log_item_widget.dart b/lib/ui/pages/more/console/console_log_item_widget.dart new file mode 100644 index 00000000..f64d1a50 --- /dev/null +++ b/lib/ui/pages/more/console/console_log_item_widget.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/models/console_log.dart'; +import 'package:selfprivacy/ui/pages/more/console/console_log_item_dialog.dart'; + +extension on ConsoleLog { + Color resolveColor(final BuildContext context) => isError + ? Theme.of(context).colorScheme.error + : switch (this) { + (final RestApiRequestConsoleLog _) => + Theme.of(context).colorScheme.secondary, + (final RestApiResponseConsoleLog _) => + Theme.of(context).colorScheme.primary, + (final GraphQlRequestConsoleLog _) => + Theme.of(context).colorScheme.secondary, + (final GraphQlResponseConsoleLog _) => + Theme.of(context).colorScheme.tertiary, + (final ManualConsoleLog _) => Theme.of(context).colorScheme.tertiary, + }; + + IconData resolveIcon() => switch (this) { + (final RestApiRequestConsoleLog _) => Icons.upload_outlined, + (final RestApiResponseConsoleLog _) => Icons.download_outlined, + (final GraphQlRequestConsoleLog _) => Icons.arrow_circle_up_outlined, + (final GraphQlResponseConsoleLog _) => Icons.arrow_circle_down_outlined, + (final ManualConsoleLog _) => Icons.read_more_outlined, + }; +} + +class ConsoleLogItemWidget extends StatelessWidget { + const ConsoleLogItemWidget({ + required this.log, + super.key, + }); + + final ConsoleLog log; + + @override + Widget build(final BuildContext context) => ListTile( + dense: true, + title: SelectableText.rich( + TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: '${log.timeString}: ', + style: const TextStyle( + fontFeatures: [FontFeature.tabularFigures()], + ), + ), + TextSpan( + text: log.title, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + subtitle: Text( + log.content, + overflow: TextOverflow.ellipsis, + maxLines: 3, + ), + leading: Icon(log.resolveIcon()), + iconColor: log.resolveColor(context), + onTap: () => showDialog( + context: context, + builder: (final BuildContext context) => ConsoleItemDialog(log: log), + ), + ); +} diff --git a/lib/ui/pages/more/console/console_page.dart b/lib/ui/pages/more/console/console_page.dart new file mode 100644 index 00000000..974887d5 --- /dev/null +++ b/lib/ui/pages/more/console/console_page.dart @@ -0,0 +1,138 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/models/console_log.dart'; +import 'package:selfprivacy/ui/pages/more/console/console_log_item_widget.dart'; + +/// listing with 500 latest app operations. +@RoutePage() +class ConsolePage extends StatefulWidget { + const ConsolePage({super.key}); + + @override + State createState() => _ConsolePageState(); +} + +class _ConsolePageState extends State { + /// should freeze logs state to properly read logs + bool paused = false; + late final Future future; + + @override + void initState() { + super.initState(); + + future = getIt.allReady(); + getIt().addListener(update); + } + + @override + void dispose() { + getIt().removeListener(update); + + super.dispose(); + } + + void update() { + /// listener update could come at any time, like when widget is already + /// unmounted or during frame build, adding as postframe callback ensures + /// that element is marked for rebuild + WidgetsBinding.instance.addPostFrameCallback((final _) { + if (!paused && mounted) { + setState(() => {}); + } + }); + } + + void togglePause() { + paused ^= true; + setState(() {}); + } + + @override + Widget build(final BuildContext context) => SafeArea( + child: Scaffold( + appBar: AppBar( + title: Text('console_page.title'.tr()), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.of(context).maybePop(), + ), + actions: [ + IconButton( + icon: Icon( + paused ? Icons.play_arrow_outlined : Icons.pause_outlined, + ), + onPressed: togglePause, + ), + ], + ), + body: SelectionArea( + child: Scrollbar( + child: FutureBuilder( + future: future, + builder: ( + final BuildContext context, + final AsyncSnapshot snapshot, + ) { + if (snapshot.hasData) { + final List messages = + getIt.get().logs; + + return _ConsoleViewLoaded( + logs: messages, + ); + } + + return const _ConsoleViewLoading(); + }, + ), + ), + ), + ), + ); +} + +class _ConsoleViewLoading extends StatelessWidget { + const _ConsoleViewLoading(); + + @override + Widget build(final BuildContext context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('console_page.waiting'.tr()), + const Gap(16), + const Expanded( + child: Center( + child: CircularProgressIndicator.adaptive(), + ), + ), + ], + ); +} + +class _ConsoleViewLoaded extends StatelessWidget { + const _ConsoleViewLoaded({required this.logs}); + + final List logs; + + @override + Widget build(final BuildContext context) => ListView.separated( + primary: true, + padding: const EdgeInsets.symmetric(vertical: 10), + itemCount: logs.length, + itemBuilder: (final BuildContext context, final int index) { + final log = logs[logs.length - 1 - index]; + + return ConsoleLogItemWidget( + key: ValueKey(log), + log: log, + ); + }, + separatorBuilder: (final context, final _) => const Divider(), + ); +} diff --git a/lib/ui/router/router.dart b/lib/ui/router/router.dart index 89c43618..14f09d82 100644 --- a/lib/ui/router/router.dart +++ b/lib/ui/router/router.dart @@ -10,7 +10,7 @@ import 'package:selfprivacy/ui/pages/dns_details/dns_details.dart'; import 'package:selfprivacy/ui/pages/more/about_application.dart'; import 'package:selfprivacy/ui/pages/more/app_settings/app_settings.dart'; import 'package:selfprivacy/ui/pages/more/app_settings/developer_settings.dart'; -import 'package:selfprivacy/ui/pages/more/console.dart'; +import 'package:selfprivacy/ui/pages/more/console/console_page.dart'; import 'package:selfprivacy/ui/pages/more/more.dart'; import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; import 'package:selfprivacy/ui/pages/providers/providers.dart'; @@ -53,6 +53,7 @@ class RootRouter extends _$RootRouter { @override RouteType get defaultRouteType => const RouteType.material(); + @override final List routes = [ AutoRoute(page: OnboardingRoute.page), From acc007894c73abfeae5c97c771b0846643deefb4 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Mon, 29 Apr 2024 18:02:23 +0400 Subject: [PATCH 11/37] feat: cleaned up connection status bloc code --- lib/config/bloc_config.dart | 2 +- .../connection_status_bloc.dart | 39 ------------------- .../connection_status_event.dart | 14 ------- .../connection_status_state.dart | 12 ------ lib/logic/bloc/connection_status_bloc.dart | 27 +++++++++++++ 5 files changed, 28 insertions(+), 66 deletions(-) delete mode 100644 lib/logic/bloc/connection_status/connection_status_bloc.dart delete mode 100644 lib/logic/bloc/connection_status/connection_status_event.dart delete mode 100644 lib/logic/bloc/connection_status/connection_status_state.dart create mode 100644 lib/logic/bloc/connection_status_bloc.dart diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 06cb7244..2a42456c 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; -import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart'; +import 'package:selfprivacy/logic/bloc/connection_status_bloc.dart'; import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart'; import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; diff --git a/lib/logic/bloc/connection_status/connection_status_bloc.dart b/lib/logic/bloc/connection_status/connection_status_bloc.dart deleted file mode 100644 index 868f05d0..00000000 --- a/lib/logic/bloc/connection_status/connection_status_bloc.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:async'; - -import 'package:equatable/equatable.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:selfprivacy/config/get_it_config.dart'; - -part 'connection_status_event.dart'; -part 'connection_status_state.dart'; - -class ConnectionStatusBloc - extends Bloc { - ConnectionStatusBloc() - : super( - const ConnectionStatusState( - connectionStatus: ConnectionStatus.nonexistent, - ), - ) { - on((final event, final emit) { - emit(ConnectionStatusState(connectionStatus: event.connectionStatus)); - }); - final apiConnectionRepository = getIt(); - _apiConnectionStatusSubscription = - apiConnectionRepository.connectionStatusStream.listen( - (final ConnectionStatus connectionStatus) { - add( - ConnectionStatusChanged(connectionStatus), - ); - }, - ); - } - - StreamSubscription? _apiConnectionStatusSubscription; - - @override - Future close() { - _apiConnectionStatusSubscription?.cancel(); - return super.close(); - } -} diff --git a/lib/logic/bloc/connection_status/connection_status_event.dart b/lib/logic/bloc/connection_status/connection_status_event.dart deleted file mode 100644 index 0fc6e72f..00000000 --- a/lib/logic/bloc/connection_status/connection_status_event.dart +++ /dev/null @@ -1,14 +0,0 @@ -part of 'connection_status_bloc.dart'; - -sealed class ConnectionStatusEvent extends Equatable { - const ConnectionStatusEvent(); -} - -class ConnectionStatusChanged extends ConnectionStatusEvent { - const ConnectionStatusChanged(this.connectionStatus); - - final ConnectionStatus connectionStatus; - - @override - List get props => [connectionStatus]; -} diff --git a/lib/logic/bloc/connection_status/connection_status_state.dart b/lib/logic/bloc/connection_status/connection_status_state.dart deleted file mode 100644 index 258765c1..00000000 --- a/lib/logic/bloc/connection_status/connection_status_state.dart +++ /dev/null @@ -1,12 +0,0 @@ -part of 'connection_status_bloc.dart'; - -class ConnectionStatusState extends Equatable { - const ConnectionStatusState({ - required this.connectionStatus, - }); - - final ConnectionStatus connectionStatus; - - @override - List get props => [connectionStatus]; -} diff --git a/lib/logic/bloc/connection_status_bloc.dart b/lib/logic/bloc/connection_status_bloc.dart new file mode 100644 index 00000000..f5315f46 --- /dev/null +++ b/lib/logic/bloc/connection_status_bloc.dart @@ -0,0 +1,27 @@ +import 'dart:async'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; + +/// basically, a bus for other blocs to listen to server status updates +class ConnectionStatusBloc extends Bloc { + ConnectionStatusBloc() : super(ConnectionStatus.nonexistent) { + on( + (final newStatus, final emit) => emit(newStatus), + ); + + final apiConnectionRepository = getIt(); + _apiConnectionStatusSubscription = + apiConnectionRepository.connectionStatusStream.listen( + (final ConnectionStatus newStatus) => add(newStatus), + ); + } + + StreamSubscription? _apiConnectionStatusSubscription; + + @override + Future close() { + _apiConnectionStatusSubscription?.cancel(); + return super.close(); + } +} From 47f3d5f53c7f6857af6493cc32f20bdfd36b96bd Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Tue, 30 Apr 2024 02:49:06 +0400 Subject: [PATCH 12/37] feat: added translations to some of console page elements, empty view when there are 0 logs in console yet. --- assets/translations/en.json | 18 ++++++++++- .../more/console/console_log_item_dialog.dart | 32 +++++++++---------- lib/ui/pages/more/console/console_page.dart | 20 +++++++++--- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index f5194cdd..50d2b715 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -47,7 +47,23 @@ "console_page": { "title": "Console", "waiting": "Waiting for initialization…", - "copy": "Copy" + "copy": "Copy", + "historyEmpty": "No data yet", + "error":"Error", + "log":"Log", + "rest_api_request":"Rest API Request", + "rest_api_response":"Rest API Response", + "graphql_request":"GraphQL Request", + "graphql_response":"GraphQL Response", + "logged_at": "logged at: ", + "data": "Data", + "erros":"Errors", + "request_data": "Request data", + "headers": "Headers", + "response_data": "Response data", + "context": "Context", + "operation": "Operation", + "variables": "Variables" }, "about_application_page": { "title": "About & support", diff --git a/lib/ui/pages/more/console/console_log_item_dialog.dart b/lib/ui/pages/more/console/console_log_item_dialog.dart index 89e7abb2..686eec77 100644 --- a/lib/ui/pages/more/console/console_log_item_dialog.dart +++ b/lib/ui/pages/more/console/console_log_item_dialog.dart @@ -10,36 +10,35 @@ extension on ConsoleLog { if (log.uri != null) _KeyValueRow('uri', log.uri.toString()), // headers bloc - if (log.headers?.isNotEmpty ?? false) const _SectionRow('Headers'), + if (log.headers?.isNotEmpty ?? false) + const _SectionRow('console_page.headers'), ...?log.headers?.entries .map((final entry) => _KeyValueRow(entry.key, entry.value)), // data bloc - const _SectionRow('Request data'), + const _SectionRow('console_page.data'), _DataRow(log.data?.toString()), ], (final RestApiResponseConsoleLog log) => [ if (log.method != null) _KeyValueRow('method', log.method), if (log.uri != null) _KeyValueRow('uri', log.uri.toString()), if (log.statusCode != null) - _KeyValueRow( - 'statusCode', - log.statusCode.toString(), - ), + _KeyValueRow('statusCode', log.statusCode.toString()), // data bloc - const _SectionRow('Response data'), + const _SectionRow('console_page.response_data'), _DataRow(log.data?.toString()), ], (final GraphQlRequestConsoleLog log) => [ // context - const _SectionRow('Context'), + const _SectionRow('console_page.context'), _DataRow(log.context?.toString()), // data - if (log.operation != null) const _SectionRow('Operation'), + if (log.operation != null) + const _SectionRow('console_page.operation'), _DataRow(log.stringifiedOperation), // errors if (log.variables?.isNotEmpty ?? false) - const _SectionRow('Variables'), + const _SectionRow('console_page.variables'), ...?log.variables?.entries.map( (final entry) => _KeyValueRow( entry.key, @@ -49,10 +48,10 @@ extension on ConsoleLog { ], (final GraphQlResponseConsoleLog log) => [ // context - const _SectionRow('Context'), + const _SectionRow('console_page.context'), _DataRow(log.context?.toString()), // data - if (log.data != null) const _SectionRow('Data'), + if (log.data != null) const _SectionRow('console_page.data'), ...?log.data?.entries.map( (final entry) => _KeyValueRow( entry.key, @@ -60,7 +59,8 @@ extension on ConsoleLog { ), ), // errors - if (log.errors?.isNotEmpty ?? false) const _SectionRow('Errors'), + if (log.errors?.isNotEmpty ?? false) + const _SectionRow('console_page.errors'), ...?log.errors?.map( (final entry) => _KeyValueRow( entry.message, @@ -94,8 +94,7 @@ class ConsoleItemDialog extends StatelessWidget { children: [ Row( children: [ - /// TODO(misterfourtytwo): maybe should add translations later - const Text('logged at: '), + Text('logged_at'.tr()), SelectableText( log.timeString, style: const TextStyle( @@ -160,6 +159,7 @@ class _DataRow extends StatelessWidget { class _SectionRow extends StatelessWidget { const _SectionRow(this.title); + final String title; @override @@ -175,7 +175,7 @@ class _SectionRow extends StatelessWidget { ), ), child: SelectableText( - title, + title.tr(), style: const TextStyle( fontWeight: FontWeight.w800, fontSize: 20, diff --git a/lib/ui/pages/more/console/console_page.dart b/lib/ui/pages/more/console/console_page.dart index 974887d5..83618ba2 100644 --- a/lib/ui/pages/more/console/console_page.dart +++ b/lib/ui/pages/more/console/console_page.dart @@ -78,12 +78,14 @@ class _ConsolePageState extends State { final AsyncSnapshot snapshot, ) { if (snapshot.hasData) { - final List messages = + final List logs = getIt.get().logs; - return _ConsoleViewLoaded( - logs: messages, - ); + return logs.isEmpty + ? const _ConsoleViewEmpty() + : _ConsoleViewLoaded( + logs: logs, + ); } return const _ConsoleViewLoading(); @@ -115,6 +117,16 @@ class _ConsoleViewLoading extends StatelessWidget { ); } +class _ConsoleViewEmpty extends StatelessWidget { + const _ConsoleViewEmpty(); + + @override + Widget build(final BuildContext context) => Align( + alignment: Alignment.topCenter, + child: Text('console_page.historyEmpty'.tr()), + ); +} + class _ConsoleViewLoaded extends StatelessWidget { const _ConsoleViewLoaded({required this.logs}); From 6eb5299d468483be1c333fe65161046e66d7097f Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Tue, 30 Apr 2024 23:25:51 +0400 Subject: [PATCH 13/37] feat: proper separate getter for clipboard content --- lib/logic/models/console_log.dart | 2 +- lib/ui/pages/more/console/console_log_item_dialog.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/logic/models/console_log.dart b/lib/logic/models/console_log.dart index 80fb6240..6d982758 100644 --- a/lib/logic/models/console_log.dart +++ b/lib/logic/models/console_log.dart @@ -33,7 +33,7 @@ sealed class ConsoleLog { /// data available for copy in dialog String? get shareableData => '$title\n' - '$content'; + '{\n$content\n}'; static final DateFormat _formatter = DateFormat('hh:mm:ss'); String get timeString => _formatter.format(time); diff --git a/lib/ui/pages/more/console/console_log_item_dialog.dart b/lib/ui/pages/more/console/console_log_item_dialog.dart index 686eec77..addd8ef9 100644 --- a/lib/ui/pages/more/console/console_log_item_dialog.dart +++ b/lib/ui/pages/more/console/console_log_item_dialog.dart @@ -112,7 +112,7 @@ class ConsoleItemDialog extends StatelessWidget { // A button to copy the request to the clipboard if (log.shareableData?.isNotEmpty ?? false) TextButton( - onPressed: () => PlatformAdapter.setClipboard(log.content), + onPressed: () => PlatformAdapter.setClipboard(log.shareableData!), child: Text('console_page.copy'.tr()), ), // close dialog From 844039e0f2f97959ddab7534a2dea11d51f1e77a Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 1 May 2024 02:59:47 +0400 Subject: [PATCH 14/37] feat: simplified digital ocean town name to country mapper. --- .../json/digital_ocean_server_info.dart | 53 +++++-------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/lib/logic/models/json/digital_ocean_server_info.dart b/lib/logic/models/json/digital_ocean_server_info.dart index dbe41f66..5e92de29 100644 --- a/lib/logic/models/json/digital_ocean_server_info.dart +++ b/lib/logic/models/json/digital_ocean_server_info.dart @@ -77,46 +77,21 @@ class DigitalOceanLocation { return emoji; } + static const _townPrefixToCountryMap = { + 'fra': 'germany', + 'ams': 'netherlands', + 'sgp': 'singapore', + 'lon': 'united_kingdom', + 'tor': 'canada', + 'blr': 'india', + 'syd': 'australia', + 'nyc': 'united_states', + 'sfo': 'united_states', + }; + String get countryDisplayKey { - String displayKey = 'countries.'; - switch (slug.substring(0, 3)) { - case 'fra': - displayKey += 'germany'; - break; - - case 'ams': - displayKey += 'netherlands'; - break; - - case 'sgp': - displayKey += 'singapore'; - break; - - case 'lon': - displayKey += 'united_kingdom'; - break; - - case 'tor': - displayKey += 'canada'; - break; - - case 'blr': - displayKey += 'india'; - break; - - case 'syd': - displayKey += 'australia'; - break; - - case 'nyc': - case 'sfo': - displayKey += 'united_states'; - break; - - default: - displayKey = slug; - } - return displayKey; + final countryName = _townPrefixToCountryMap[slug.substring(0, 3)] ?? slug; + return 'countries.$countryName'; } } From c2a77b9fc5249f8f2fe2c373d1c19e9e3453e70c Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 1 May 2024 03:19:18 +0400 Subject: [PATCH 15/37] fix: removed duplicate insertion of localization widget (was both in main and in app widget) --- lib/main.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d11ecd6b..4eb1933c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -42,11 +42,9 @@ void main() async { Bloc.observer = SimpleBlocObserver(); runApp( - Localization( - child: SelfprivacyApp( - lightThemeData: lightThemeData, - darkThemeData: darkThemeData, - ), + SelfprivacyApp( + lightThemeData: lightThemeData, + darkThemeData: darkThemeData, ), ); } From 5033fa3b49bd08960bcd0d5975cc78778113b1e4 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 14:41:31 +0400 Subject: [PATCH 16/37] chore: version bump, changed discountinued `package_info` to `package_info_plus` --- lib/ui/pages/more/about_application.dart | 2 +- pubspec.lock | 122 +++++++++++------------ pubspec.yaml | 28 +++--- 3 files changed, 76 insertions(+), 76 deletions(-) diff --git a/lib/ui/pages/more/about_application.dart b/lib/ui/pages/more/about_application.dart index 9e6cae65..b2fd41fc 100644 --- a/lib/ui/pages/more/about_application.dart +++ b/lib/ui/pages/more/about_application.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:package_info/package_info.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/ui/components/list_tiles/section_title.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; diff --git a/pubspec.lock b/pubspec.lock index b6bf205d..7187e937 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: archive - sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265 url: "https://pub.dev" source: hosted - version: "3.4.10" + version: "3.5.1" args: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: auto_route - sha256: f04022b2a5c4d255f7feb139a75cb3d100ccd2f8918a75036fe09456309a13a3 + sha256: "6cad3f408863ffff2b5757967c802b18415dac4acb1b40c5cdd45d0a26e5080f" url: "https://pub.dev" source: hosted - version: "8.0.3" + version: "8.1.3" auto_route_generator: dependency: "direct dev" description: @@ -253,10 +253,10 @@ packages: dependency: "direct main" description: name: cubit_form - sha256: "3e01bb30b0b9fc9c0807536b08865714487d26256566bdbb31e780f51d9a8932" + sha256: d4e0afd9ca95ee5602a26e9d678ee4f591f98b52539906cc0e23c54c3a71062b url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" dart_style: dependency: transitive description: @@ -317,10 +317,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c + sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.7" easy_logger: dependency: transitive description: @@ -381,10 +381,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "2b7c1f5d867da9a054661641c8f499c55c47c39acccb97b3bc673f5fa9a39e74" + sha256: d0f0d49112f2f4b192481c16d05b6418bd7820e021e265a3c22db98acf7ed7fb url: "https://pub.dev" source: hosted - version: "0.67.0" + version: "0.68.0" flutter: dependency: "direct main" description: flutter @@ -431,10 +431,10 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: "04c4722cc36ec5af38acc38ece70d22d3c2123c61305d555750a091517bbe504" + sha256: "9921f9deda326f8a885e202b1e35237eadfc1345239a0f6f0f1ff287e047547f" url: "https://pub.dev" source: hosted - version: "0.6.23" + version: "0.7.1" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -447,50 +447,50 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 + sha256: c0f402067fb0498934faa6bddd670de0a3db45222e2ca9a068c6177c9a2360a4 url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.1.1" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" + sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c + sha256: "8cfa53010a294ff095d7be8fa5bb15f2252c50018d69c5104851303f3ff92510" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" + sha256: "301f67ee9b87f04aef227f57f13f126fa7b13543c8e7a93f25c5d2d534c28a4a" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.2" flutter_svg: dependency: "direct main" description: @@ -545,18 +545,18 @@ packages: dependency: "direct main" description: name: gql - sha256: afe032332ddfa69b79f1dea2ad7d95923d4993c1b269b224fc7bb3d17e32d33c + sha256: "5877b9e6537eb69431df7a45959d3f137ba5b7eba73208a631c404f36600e259" url: "https://pub.dev" source: hosted - version: "1.0.1-alpha+1709845491443" + version: "1.0.1-alpha+1715521079501" gql_dedupe_link: dependency: transitive description: name: gql_dedupe_link - sha256: "2971173c68623d5c43f5327ea899bd2ee64ce3461c1263f240b4bb6211f667be" + sha256: "10bee0564d67c24e0c8bd08bd56e0682b64a135e58afabbeed30d85d5e9fea96" url: "https://pub.dev" source: hosted - version: "2.0.4-alpha+1709845491527" + version: "2.0.4-alpha+1715521079596" gql_error_link: dependency: transitive description: @@ -585,10 +585,10 @@ packages: dependency: transitive description: name: gql_link - sha256: "177500e250b3742d6d2673d57961e8413b6593dc6bd6a512c51865b6cf096f7e" + sha256: a0dae65d022285564ad333763a6988a603d6b9a075760ae178d6d22586bd342b url: "https://pub.dev" source: hosted - version: "1.0.1-alpha+1709845491457" + version: "1.0.1-alpha+1715521079517" gql_transform_link: dependency: transitive description: @@ -713,18 +713,18 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b url: "https://pub.dev" source: hosted - version: "6.7.1" + version: "6.8.0" leak_tracker: dependency: transitive description: @@ -777,10 +777,10 @@ packages: dependency: transitive description: name: local_auth_darwin - sha256: "33381a15b0de2279523eca694089393bb146baebdce72a404555d03174ebc1e9" + sha256: "959145a4cf6f0de745b9ec9ac60101270eb4c5b8b7c2a0470907014adc1c618d" url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" local_auth_platform_interface: dependency: transitive description: @@ -901,14 +901,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" - package_info: + package_info_plus: dependency: "direct main" description: - name: package_info - sha256: "6c07d9d82c69e16afeeeeb6866fe43985a20b3b50df243091bfc4a4ad2b03b75" + name: package_info_plus + sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "8.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e + url: "https://pub.dev" + source: hosted + version: "3.0.0" path: dependency: transitive description: @@ -945,10 +953,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -997,14 +1005,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" - url: "https://pub.dev" - source: hosted - version: "3.8.0" pool: dependency: transitive description: @@ -1073,10 +1073,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -1214,10 +1214,10 @@ packages: dependency: "direct main" description: name: timezone - sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0" + sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5 url: "https://pub.dev" source: hosted - version: "0.9.2" + version: "0.9.3" timing: dependency: transitive description: @@ -1254,10 +1254,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" + sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89" url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.3.0" url_launcher_linux: dependency: transitive description: @@ -1270,10 +1270,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: @@ -1374,10 +1374,10 @@ packages: dependency: transitive description: name: win32 - sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.5.0" win32_registry: dependency: transitive description: @@ -1411,5 +1411,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.3.1 <4.0.0" flutter: ">=3.19.5" diff --git a/pubspec.yaml b/pubspec.yaml index 8911beb7..6c7ad7d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,23 +9,23 @@ environment: dependencies: animations: ^2.0.11 - auto_route: ^8.0.3 + auto_route: ^8.1.3 auto_size_text: ^3.0.0 bloc_concurrency: ^0.2.5 - crypt: ^4.3.1 collection: ^1.18.0 + crypt: ^4.3.1 cubit_form: ^2.0.1 device_info_plus: ^10.0.1 dio: ^5.4.2+1 duration: ^3.0.13 dynamic_color: ^1.7.0 - easy_localization: ^3.0.5 + easy_localization: ^3.0.6 equatable: ^2.0.5 - fl_chart: ^0.67.0 + fl_chart: ^0.68.0 flutter: sdk: flutter flutter_bloc: ^8.1.5 - flutter_markdown: ^0.6.22 + flutter_markdown: ^0.7.1 flutter_secure_storage: ^9.0.0 flutter_svg: ^2.0.10+1 gap: ^3.0.1 @@ -38,29 +38,29 @@ dependencies: http: ^1.2.1 intl: ^0.18.1 ionicons: ^0.2.2 - json_annotation: ^4.8.1 + json_annotation: ^4.9.0 local_auth: ^2.2.0 material_color_utilities: ^0.8.0 modal_bottom_sheet: ^3.0.0-pre nanoid: ^1.0.0 - package_info: ^2.0.2 + package_info_plus: ^8.0.0 pretty_dio_logger: ^1.3.1 provider: ^6.1.2 pub_semver: ^2.1.4 - timezone: ^0.9.2 + timezone: ^0.9.3 url_launcher: ^6.2.5 - # TODO: Developer is not available, update later. -# wakelock: ^0.6.2 +# wakelock: ^0.6.2 # TODO: Developer is not available, update later. + dev_dependencies: auto_route_generator: ^8.0.0 - flutter_test: - sdk: flutter build_runner: ^2.4.9 flutter_launcher_icons: ^0.13.1 - hive_generator: ^2.0.1 - json_serializable: ^6.7.1 flutter_lints: ^3.0.2 + flutter_test: + sdk: flutter + hive_generator: ^2.0.1 + json_serializable: ^6.8.0 flutter_icons: android: "launcher_icon" From 1c8cb82e2a9b35b2846578e8d2d31d1e89296b40 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 14:43:38 +0400 Subject: [PATCH 17/37] feat(strings): added new `application_settings` strings, sorted keys to correspond to ui order, fixed some `be` translations --- assets/translations/ar.json | 8 +++----- assets/translations/az.json | 14 +++++++------- assets/translations/be.json | 28 ++++++++++++++-------------- assets/translations/cs.json | 12 +++++------- assets/translations/de.json | 12 +++++------- assets/translations/en.json | 8 +++++--- assets/translations/es.json | 14 ++++++-------- assets/translations/et.json | 10 ++++------ assets/translations/fr.json | 14 ++++++-------- assets/translations/he.json | 10 ++++------ assets/translations/kk.json | 14 ++++++-------- assets/translations/lv.json | 12 +++++------- assets/translations/pl.json | 12 +++++------- assets/translations/ru.json | 14 +++++++------- assets/translations/sk.json | 12 +++++------- assets/translations/sl.json | 12 +++++------- assets/translations/th.json | 6 ++---- assets/translations/uk.json | 15 +++++++-------- assets/translations/zh-Hans.json | 10 ++++------ 19 files changed, 105 insertions(+), 132 deletions(-) diff --git a/assets/translations/ar.json b/assets/translations/ar.json index 4c02806f..d77b1ba4 100644 --- a/assets/translations/ar.json +++ b/assets/translations/ar.json @@ -333,14 +333,12 @@ }, "application_settings": { "title": "إعدادات التطبيق", - "system_dark_theme_title": "الوضع الافتراضي للنظام", - "system_dark_theme_description": "قم بتطبيق الوضع الفاتح أو الداكن حسب إعدادات النظام", + "system_theme_mode_title": "الوضع الافتراضي للنظام", + "system_theme_mode_description": "قم بتطبيق الوضع الفاتح أو الداكن حسب إعدادات النظام", "dark_theme_title": "الوضع الداكن", + "change_application_theme": "قم بتبديل وضع التطبيق", "dangerous_settings": "إعدادات خطرة", "reset_config_title": "قم بإعادة ضبط إعدادات التطبيق", - "delete_server_title": "قم بحذف الخادم", - "delete_server_description": "سيزيل هذا الخادم الخاص بك، حيث أنه لن تتمكن من الوصول إليه بعد ذلك.", - "dark_theme_description": "قم بتبديل وضع التطبيق", "reset_config_description": "قم بإعادة ضبط مفاتيح API والمستخدم المميز." }, "ssh": { diff --git a/assets/translations/az.json b/assets/translations/az.json index db509e48..8f8cc8d2 100644 --- a/assets/translations/az.json +++ b/assets/translations/az.json @@ -54,15 +54,15 @@ }, "application_settings": { "title": "Tətbiq parametrləri", + "system_theme_mode_title": "Defolt sistem mövzusu", + "system_theme_mode_description": "Sistem parametrlərindən asılı olaraq açıq və ya qaranlıq mövzudan istifadə edin", "dark_theme_title": "Qaranlıq mövzu", + "change_application_theme": "Rəng mövzusunu dəyişdirin", + "dangerous_settings": "Təhlükəli Parametrlər", "reset_config_title": "Tətbiq Sıfırlayın", - "reset_config_description": "API və Super İstifadəçi Açarlarını sıfırlayın.", - "delete_server_title": "Serveri silin", - "dark_theme_description": "Rəng mövzusunu dəyişdirin", - "delete_server_description": "Əməliyyat serveri siləcək. Bundan sonra o, əlçatmaz olacaq.", - "system_dark_theme_title": "Defolt sistem mövzusu", - "system_dark_theme_description": "Sistem parametrlərindən asılı olaraq açıq və ya qaranlıq mövzudan istifadə edin", - "dangerous_settings": "Təhlükəli Parametrlər" + "reset_config_description": "API və Super İstifadəçi Açarlarını sıfırlayın." + + }, "ssh": { "title": "SSH açarları", diff --git a/assets/translations/be.json b/assets/translations/be.json index 1f1b0c86..4c85255b 100644 --- a/assets/translations/be.json +++ b/assets/translations/be.json @@ -51,7 +51,7 @@ "connect_to_server_provider": "Аўтарызавацца ў ", "connect_to_server_provider_text": "З дапамогай API токена праграма SelfPrivacy зможа ад вашага імя замовіць і наладзіць сервер", "steps": { - "nixos_installation": "Ўстаноўка NixOS", + "nixos_installation": "Ўсталёўка NixOS", "hosting": "Хостынг", "server_type": "Тып сервера", "dns_provider": "DNS правайдэр", @@ -59,7 +59,7 @@ "domain": "Дамен", "master_account": "Майстар акаўнт", "server": "Сервер", - "dns_setup": "Устаноўка DNS", + "dns_setup": "Усталёўка DNS", "server_reboot": "Перазагрузка сервера", "final_checks": "Фінальныя праверкі" }, @@ -100,7 +100,7 @@ "modal_confirmation_dns_invalid": "Зваротны DNS паказвае на іншы дамен", "modal_confirmation_ip_invalid": "IP не супадае з паказаным у DNS запісу", "fallback_select_provider_console": "Доступ да кансолі хостынгу.", - "provider_connected_description": "Сувязь устаноўлена. Увядзіце свой токен з доступам да {}:", + "provider_connected_description": "Сувязь наладжана. Увядзіце свой токен з доступам да {}:", "choose_server": "Выберыце сервер", "no_servers": "На вашым акаўнце няма даступных сэрвэраў.", "modal_confirmation_description": "Падлучэнне да няправільнага сервера можа прывесці да дэструктыўных наступстваў.", @@ -114,7 +114,7 @@ "authorize_new_device": "Аўтарызаваць новую прыладу", "access_granted_on": "Доступ выдадзены {}", "tip": "Націсніце на прыладу, каб адклікаць доступ.", - "description": "Гэтыя прылады маюць поўны доступ да кіравання серверам праз прыкладанне SelfPrivacy." + "description": "Гэтыя прылады маюць поўны доступ да кіравання серверам праз прыладу SelfPrivacy." }, "add_new_device_screen": { "description": "Увядзіце гэты ключ на новай прыладзе:", @@ -127,7 +127,7 @@ "revoke_device_alert": { "header": "Адклікаць доступ?", "yes": "Адклікаць", - "no": "Адменіць", + "no": "Адхіліць", "description": "Прылада {} больш не зможа кіраваць серверам." } }, @@ -143,7 +143,7 @@ "later": "Прапусціць і наладзіць потым", "no_data": "Няма дадзеных", "services": "Сэрвісы", - "users": "Ужыткоўнікі", + "users": "Карыстальнікі", "more": "Дадаткова", "got_it": "Зразумеў", "settings": "Налады", @@ -234,7 +234,7 @@ }, "more_page": { "configuration_wizard": "Майстар наладкі", - "onboarding": "Прівітанне", + "onboarding": "Прывітанне", "create_ssh_key": "SSH ключы адміністратара" }, "about_application_page": { @@ -244,16 +244,16 @@ "privacy_policy": "Палітыка прыватнасці" }, "application_settings": { - "reset_config_description": "Скінуць API ключы i суперкарыстальніка.", - "delete_server_description": "Дзеянне прывядзе да выдалення сервера. Пасля гэтага ён будзе недаступны.", "title": "Налады праграмы", + "system_theme_mode_title": "Сістэмная тэма па-змаўчанні", + "system_theme_mode_description": "Выкарыстоўвайце светлую ці цёмную тэмы ў залежнасці ад сістэмных налад", "dark_theme_title": "Цёмная тэма", - "dark_theme_description": "Змяніць каляровую тэму", + "change_application_theme": "Змяніць каляровую тэму", + "language": "Мова", + "click_to_change_locale":"Націсніце, каб адчыніць меню выбару мовы", + "dangerous_settings": "Небяспечныя налады", "reset_config_title": "Скід налад", - "delete_server_title": "Выдаліць сервер", - "system_dark_theme_title": "Сістэмная тэма па-змаўчанні", - "system_dark_theme_description": "Выкарыстоўвайце светлую ці цёмную тэмы ў залежнасці ад сістэмных налад", - "dangerous_settings": "Небяспечныя наладкі" + "reset_config_description": "Скінуць API ключы i суперкарыстальніка." }, "ssh": { "root_subtitle": "Уладальнікі паказаных тут ключоў атрымліваюць поўны доступ да дадзеных і налад сервера. Дадавайце выключна свае ключы.", diff --git a/assets/translations/cs.json b/assets/translations/cs.json index b9206448..29f0dcc9 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -54,15 +54,13 @@ }, "application_settings": { "title": "Nastavení aplikace", + "system_theme_mode_title": "Výchozí téma systému", + "system_theme_mode_description": "Použití světlého nebo tmavého motivu v závislosti na nastavení systému", "dark_theme_title": "Tmavé téma", + "change_application_theme": "Přepnutí tématu aplikace", + "dangerous_settings": "Nebezpečná nastavení", "reset_config_title": "Obnovení konfigurace aplikace", - "reset_config_description": "Obnovení klíčů API a uživatele root.", - "delete_server_title": "Odstranit server", - "dark_theme_description": "Přepnutí tématu aplikace", - "delete_server_description": "Tím odstraníte svůj server. Nebude již přístupný.", - "system_dark_theme_title": "Výchozí téma systému", - "system_dark_theme_description": "Použití světlého nebo tmavého motivu v závislosti na nastavení systému", - "dangerous_settings": "Nebezpečná nastavení" + "reset_config_description": "Obnovení klíčů API a uživatele root." }, "ssh": { "title": "Klíče SSH", diff --git a/assets/translations/de.json b/assets/translations/de.json index fb75209a..354b8519 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -57,16 +57,14 @@ }, "application_settings": { "title": "Anwendungseinstellungen", + "system_theme_mode_title": "Standard-Systemthema", + "system_theme_mode_description": "Verwenden Sie je nach Systemeinstellungen ein helles oder dunkles Thema", "dark_theme_title": "Dunkles Thema", - "dark_theme_description": "Ihr Anwendungsdesign wechseln", + "change_application_theme": "Ihr Anwendungsdesign wechseln", + "dangerous_settings": "Gefährliche Einstellungen", "reset_config_title": "Anwendungseinstellungen zurücksetzen", "reset_config_description": "API Sclüssel und root Benutzer zurücksetzen.", - "delete_server_title": "Server löschen", - "delete_server_description": "Das wird Ihren Server löschen. Es wird nicht mehr zugänglich sein.", - "system_dark_theme_title": "Standard-Systemthema", - "system_dark_theme_description": "Verwenden Sie je nach Systemeinstellungen ein helles oder dunkles Thema", - "dangerous_settings": "Gefährliche Einstellungen" - }, + }, "ssh": { "title": "SSH Schlüssel", "create": "SSH Schlüssel erstellen", diff --git a/assets/translations/en.json b/assets/translations/en.json index 50d2b715..d9cb98ab 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -91,10 +91,12 @@ }, "application_settings": { "title": "Application settings", - "system_dark_theme_title": "System default theme", - "system_dark_theme_description": "Use light or dark theme depending on system settings", + "system_theme_mode_title": "System default theme", + "system_theme_mode_description": "Use light or dark theme depending on system settings", "dark_theme_title": "Dark theme", - "dark_theme_description": "Switch your application theme", + "change_application_theme": "Switch your application theme", + "language": "Language", + "click_to_change_locale": "Click to open language list", "dangerous_settings": "Dangerous settings", "reset_config_title": "Reset application config", "reset_config_description": "Resets API keys and root user." diff --git a/assets/translations/es.json b/assets/translations/es.json index ce76f0b7..0f154fb1 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -39,16 +39,14 @@ "test": "es-test", "locale": "es", "application_settings": { - "reset_config_title": "Restablecer la configuración de la aplicación", - "dark_theme_description": "Cambia el tema de tu aplicación", - "reset_config_description": "Restablecer claves API y usuario root.", - "delete_server_title": "Eliminar servidor", - "delete_server_description": "Esto elimina su servidor. Ya no será accesible.", "title": "Ajustes de la aplicación", + "system_theme_mode_title": "Tema del sistema", + "system_theme_mode_description": "Utiliza un tema claro u oscuro de la configuración del sistema", "dark_theme_title": "Tema oscuro", - "system_dark_theme_title": "Tema del sistema", - "system_dark_theme_description": "Utiliza un tema claro u oscuro de la configuración del sistema", - "dangerous_settings": "Configuraciones peligrosas" + "change_application_theme": "Cambia el tema de tu aplicación", + "dangerous_settings": "Configuraciones peligrosas", + "reset_config_title": "Restablecer la configuración de la aplicación", + "reset_config_description": "Restablecer claves API y usuario root." }, "ssh": { "delete_confirm_question": "¿Está seguro de que desea eliminar la clave SSH?", diff --git a/assets/translations/et.json b/assets/translations/et.json index d2587053..2047faed 100644 --- a/assets/translations/et.json +++ b/assets/translations/et.json @@ -1,15 +1,13 @@ { "application_settings": { - "system_dark_theme_description": "Kasutage valgus- või tumeteemat sõltuvalt süsteemi seadetest", - "delete_server_description": "See eemaldab teie serveri. Seda ei saa enam juurde pääseda.", "title": "Rakenduse seaded", - "system_dark_theme_title": "Süsteemi vaiketeema", + "system_theme_mode_title": "Süsteemi vaiketeema", + "system_theme_mode_description": "Kasutage valgus- või tumeteemat sõltuvalt süsteemi seadetest", "dark_theme_title": "Tume teema", - "dark_theme_description": "Vaheta oma rakenduse teemat", + "change_application_theme": "Vaheta oma rakenduse teemat", "dangerous_settings": "Ohtlikud seaded", "reset_config_title": "Lähtesta rakenduse konfiguratsioon", - "reset_config_description": "Lähtestab API võtmed ja juurkasutaja.", - "delete_server_title": "Kustuta server" + "reset_config_description": "Lähtestab API võtmed ja juurkasutaja." }, "server": { "reboot_after_upgrade": "Taaskäivita pärast värskendust", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index f1d1a42f..7bf3b3b5 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -56,15 +56,13 @@ }, "application_settings": { "title": "Paramètres de l'application", - "dark_theme_description": "Changer le thème de l'application", - "reset_config_title": "Réinitialiser la configuration de l'application", - "delete_server_title": "Supprimer le serveur", - "delete_server_description": "Cela va supprimer votre serveur. Celui-ci ne sera plus accessible.", + "system_theme_mode_title": "Thème par défaut du système", + "system_theme_mode_description": "Affichage de jour ou de nuit en fonction du paramétrage système", "dark_theme_title": "Thème sombre", - "reset_config_description": "Réinitialiser les clés API et l'utilisateur root.", - "system_dark_theme_title": "Thème par défaut du système", - "system_dark_theme_description": "Affichage de jour ou de nuit en fonction du paramétrage système", - "dangerous_settings": "Paramètres dangereux" + "change_application_theme": "Changer le thème de l'application", + "dangerous_settings": "Paramètres dangereux", + "reset_config_title": "Réinitialiser la configuration de l'application", + "reset_config_description": "Réinitialiser les clés API et l'utilisateur root." }, "ssh": { "title": "Clés SSH", diff --git a/assets/translations/he.json b/assets/translations/he.json index 750b3aa5..3e20a6fd 100644 --- a/assets/translations/he.json +++ b/assets/translations/he.json @@ -81,15 +81,13 @@ }, "application_settings": { "title": "הגדרות יישום", - "system_dark_theme_title": "ערכת העיצוב כברירת המחדל של המערכת", - "system_dark_theme_description": "להשתמש בערכות עיצוב בהירה או כהה בהתאם להגדרות המערכת שלך", + "system_theme_mode_title": "ערכת העיצוב כברירת המחדל של המערכת", + "system_theme_mode_description": "להשתמש בערכות עיצוב בהירה או כהה בהתאם להגדרות המערכת שלך", "dark_theme_title": "ערכת עיצוב כהה", - "dark_theme_description": "החלפת ערכת העיצוב של המערכת שלך", + "change_application_theme": "החלפת ערכת העיצוב של המערכת שלך", "dangerous_settings": "הגדרות מסוכנות", "reset_config_title": "איפוס הגדרות היישומון", - "reset_config_description": "איפוס מפתחות ה־API ומשתמש העל.", - "delete_server_title": "מחיקת שרת", - "delete_server_description": "מסיר את השרת שלך. הוא לא יהיה זמין עוד." + "reset_config_description": "איפוס מפתחות ה־API ומשתמש העל." }, "backup": { "create_new_select_heading": "לבחור מה לגבות", diff --git a/assets/translations/kk.json b/assets/translations/kk.json index e1f99d2b..dd220f69 100644 --- a/assets/translations/kk.json +++ b/assets/translations/kk.json @@ -91,16 +91,14 @@ "bug_report_subtitle": "Спамға байланысты есептік жазбаны қолмен растау қажет. Тіркелгіні белсендіру үшін Қолдау чатында бізге хабарласыңыз." }, "application_settings": { + "title": "Қосымша параметрлері", + "system_theme_mode_title": "Системалық қараңғы тақырып", + "system_theme_mode_description": "Системалық қараңғы тақырып сипаттамасы", + "dark_theme_title": "Қараңғы тақырып", + "change_application_theme": "Қараңғы тақырып сипаттамасы", "dangerous_settings": "Қауіпті параметрлер", "reset_config_title": "Конфигурацияны қалпына келтіру", - "title": "Қосымша параметрлері", - "system_dark_theme_title": "Системалық қараңғы тақырып", - "system_dark_theme_description": "Системалық қараңғы тақырып сипаттамасы", - "dark_theme_title": "Қараңғы тақырып", - "dark_theme_description": "Қараңғы тақырып сипаттамасы", - "delete_server_title": "Серверді жою", - "reset_config_description": "Конфигурацияны қалпына келтіру сипаттамасы.", - "delete_server_description": "Серверді жою сипаттамасы." + "reset_config_description": "Конфигурацияны қалпына келтіру сипаттамасы." }, "resource_chart": { "month": "Ай", diff --git a/assets/translations/lv.json b/assets/translations/lv.json index 7ac2fefa..47a09b86 100644 --- a/assets/translations/lv.json +++ b/assets/translations/lv.json @@ -52,16 +52,14 @@ "privacy_policy": "Privātuma politika" }, "application_settings": { - "system_dark_theme_title": "Sistēmas noklusējuma dizains", - "dark_theme_title": "Tumšs dizains", "title": "Aplikācijas iestatījumi", - "system_dark_theme_description": "Izmantojiet gaišu vai tumšu dizainu atkarībā no sistēmas iestatījumiem", - "dark_theme_description": "Lietojumprogrammas dizaina pārslēgšana", + "system_theme_mode_title": "Sistēmas noklusējuma dizains", + "system_theme_mode_description": "Izmantojiet gaišu vai tumšu dizainu atkarībā no sistēmas iestatījumiem", + "dark_theme_title": "Tumšs dizains", + "change_application_theme": "Lietojumprogrammas dizaina pārslēgšana", "dangerous_settings": "Bīstamie iestatījumi", "reset_config_title": "Atiestatīt lietojumprogrammas konfigurāciju", - "reset_config_description": "Atiestatīt API atslēgas un saknes lietotāju.", - "delete_server_title": "Izdzēst serveri", - "delete_server_description": "Šis izdzēš jūsu serveri. Tas vairs nebūs pieejams." + "reset_config_description": "Atiestatīt API atslēgas un saknes lietotāju." }, "locale": "lv", "ssh": { diff --git a/assets/translations/pl.json b/assets/translations/pl.json index 7ed37915..d4fe660c 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -56,15 +56,13 @@ }, "application_settings": { "title": "Ustawienia aplikacji", + "system_theme_mode_description": "Użyj jasnego lub ciemnego motywu w zależności od ustawień systemu", + "system_theme_mode_title": "Domyślny motyw systemowy", "dark_theme_title": "Ciemny motyw aplikacji", - "dark_theme_description": "Zmień kolor motywu aplikacji", + "change_application_theme": "Zmień kolor motywu aplikacji", + "dangerous_settings": "Niebezpieczne ustawienia", "reset_config_title": "Resetowanie", - "reset_config_description": "Zresetuj klucze API i użytkownika root.", - "delete_server_title": "Usuń serwer", - "delete_server_description": "Ta czynność usunie serwer. Po tym będzie niedostępny.", - "system_dark_theme_description": "Użyj jasnego lub ciemnego motywu w zależności od ustawień systemu", - "system_dark_theme_title": "Domyślny motyw systemowy", - "dangerous_settings": "Niebezpieczne ustawienia" + "reset_config_description": "Zresetuj klucze API i użytkownika root." }, "ssh": { "title": "klucze SSH", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index b983bde1..2976e90f 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -75,15 +75,15 @@ }, "application_settings": { "title": "Настройки приложения", + "system_theme_mode_title": "Системная тема", + "system_theme_mode_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек", "dark_theme_title": "Тёмная тема", - "dark_theme_description": "Сменить цветовую тему", + "change_application_theme": "Сменить цветовую тему", + "language": "Язык", + "click_to_change_locale": "Нажмите, чтобы открыть список языков", + "dangerous_settings": "Опасные настройки", "reset_config_title": "Сброс настроек", - "reset_config_description": "Сбросить API ключи и root пользователя.", - "delete_server_title": "Удалить сервер", - "delete_server_description": "Действие приведёт к удалению сервера. После этого он будет недоступен.", - "system_dark_theme_title": "Системная тема", - "system_dark_theme_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек", - "dangerous_settings": "Опасные настройки" + "reset_config_description": "Сбросить API ключи и root пользователя." }, "ssh": { "title": "SSH ключи", diff --git a/assets/translations/sk.json b/assets/translations/sk.json index 62061c08..499204c8 100644 --- a/assets/translations/sk.json +++ b/assets/translations/sk.json @@ -103,15 +103,13 @@ }, "application_settings": { "title": "Nastavenia aplikácie", + "system_theme_mode_description": "Použitie svetlej alebo tmavej témy v závislosti od nastavení systému", + "system_theme_mode_title": "Systémová predvolená téma", "dark_theme_title": "Temná téma", - "dark_theme_description": "Zmeniť tému aplikácie", + "change_application_theme": "Zmeniť tému aplikácie", + "dangerous_settings": "Nebezpečné nastavenia", "reset_config_title": "Resetovať nastavenia aplikácie", - "reset_config_description": "Resetovať kľúče API a užívateľa root.", - "delete_server_title": "Zmazať server", - "delete_server_description": "Tým sa odstráni váš server. Už nebude prístupným.", - "system_dark_theme_description": "Použitie svetlej alebo tmavej témy v závislosti od nastavení systému", - "system_dark_theme_title": "Systémová predvolená téma", - "dangerous_settings": "Nebezpečné nastavenia" + "reset_config_description": "Resetovať kľúče API a užívateľa root." }, "ssh": { "title": "Kľúče SSH", diff --git a/assets/translations/sl.json b/assets/translations/sl.json index 78e955d4..5715cb43 100644 --- a/assets/translations/sl.json +++ b/assets/translations/sl.json @@ -53,15 +53,13 @@ "application_version_text": "Različica aplikacije" }, "application_settings": { - "dark_theme_title": "Temna tema", "title": "Nastavitve aplikacije", - "system_dark_theme_title": "Privzeta tema sistema", - "system_dark_theme_description": "Uporaba svetle ali temne teme glede na sistemske nastavitve", - "dark_theme_description": "Spreminjanje barvne teme", + "system_theme_mode_title": "Privzeta tema sistema", + "system_theme_mode_description": "Uporaba svetle ali temne teme glede na sistemske nastavitve", + "dark_theme_title": "Temna tema", + "change_application_theme": "Spreminjanje barvne teme", "dangerous_settings": "Nevarne nastavitve", - "reset_config_title": "Ponastavitev konfiguracije aplikacije", - "delete_server_title": "Brisanje strežnika", - "delete_server_description": "To dejanje povzroči izbris strežnika. Nato bo nedosegljiv." + "reset_config_title": "Ponastavitev konfiguracije aplikacije" }, "onboarding": { "page1_title": "Digitalna neodvisnost je na voljo vsem", diff --git a/assets/translations/th.json b/assets/translations/th.json index 351e85a6..462a495e 100644 --- a/assets/translations/th.json +++ b/assets/translations/th.json @@ -47,13 +47,11 @@ "privacy_policy": "นโยบายความเป็นส่วนตัว" }, "application_settings": { - "dark_theme_description": "สลับธีมแอปพลิเคชั่นของคุณ", - "delete_server_description": "การกระทำนี้จะลบเซิฟเวอร์ของคุณทิ้งและคุณจะไม่สามารถเข้าถึงมันได้อีก", "title": "การตั้งค่าแอปพลิเคชัน", "dark_theme_title": "ธีมมืด", + "change_application_theme": "สลับธีมแอปพลิเคชั่นของคุณ", "reset_config_title": "รีเซ็ตค่าดั้งเดิมการตั้งค่าของแอปพลิเคชั่น", - "reset_config_description": "รีเซ็ต API key และผู้ใช้งาน root", - "delete_server_title": "ลบเซิฟเวอร์" + "reset_config_description": "รีเซ็ต API key และผู้ใช้งาน root" }, "ssh": { "create": "สร้างกุญแจ SSH", diff --git a/assets/translations/uk.json b/assets/translations/uk.json index ee370817..9cbfac19 100644 --- a/assets/translations/uk.json +++ b/assets/translations/uk.json @@ -41,15 +41,14 @@ "locale": "ua", "application_settings": { "title": "Налаштування додатка", - "reset_config_title": "Скинути налаштування", + "system_theme_mode_title": "Системна тема за замовчуванням", + "system_theme_mode_description": "Використовуйте світлу або темну теми залежно від системних налаштувань", "dark_theme_title": "Темна тема", - "dark_theme_description": "Змінити тему додатка", - "reset_config_description": "Скинути API ключі та root користувача.", - "delete_server_title": "Видалити сервер", - "delete_server_description": "Це видалить ваш сервер. Він більше не буде доступний.", - "system_dark_theme_title": "Системна тема за замовчуванням", - "system_dark_theme_description": "Використовуйте світлу або темну теми залежно від системних налаштувань", - "dangerous_settings": "Небезпечні налаштування" + "change_application_theme": "Змінити тему додатка", + "language": "Мова", + "dangerous_settings": "Небезпечні налаштування", + "reset_config_title": "Скинути налаштування", + "reset_config_description": "Скинути API ключі та root користувача." }, "ssh": { "delete_confirm_question": "Ви впевнені, що хочете видалити SSH-ключ?", diff --git a/assets/translations/zh-Hans.json b/assets/translations/zh-Hans.json index 73c0628b..81dfd31e 100644 --- a/assets/translations/zh-Hans.json +++ b/assets/translations/zh-Hans.json @@ -476,15 +476,13 @@ }, "application_settings": { "title": "应用设置", - "system_dark_theme_title": "系统默认主题", + "system_theme_mode_title": "系统默认主题", + "system_theme_mode_description": "根据系统设置自动使用明亮或暗色主题", "dark_theme_title": "暗色主题", - "system_dark_theme_description": "根据系统设置自动使用明亮或暗色主题", - "dark_theme_description": "切换应用主题", + "change_application_theme": "切换应用主题", "dangerous_settings": "危险设置", "reset_config_title": "重置应用配置", - "delete_server_title": "删除服务器", - "delete_server_description": "这将移除您的服务器。它将不再可以访问。", - "reset_config_description": "重置API密钥和root用户。" + "delete_server_title": "删除服务器" }, "ssh": { "title": "SSH密钥", From 161c5b7fc5e6516ba9575a103658587057980ce4 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 14:45:24 +0400 Subject: [PATCH 18/37] fix: made root destination list const, removed translations from objects --- lib/ui/router/root_destinations.dart | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/ui/router/root_destinations.dart b/lib/ui/router/root_destinations.dart index 0d981894..3bd749ba 100644 --- a/lib/ui/router/root_destinations.dart +++ b/lib/ui/router/root_destinations.dart @@ -1,5 +1,4 @@ import 'package:auto_route/auto_route.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/router/router.dart'; @@ -18,29 +17,29 @@ class RouteDestination { final String title; } -final rootDestinations = [ +const List rootDestinations = [ RouteDestination( - route: const ProvidersRoute(), + route: ProvidersRoute(), icon: BrandIcons.server, - label: 'basis.providers'.tr(), - title: 'basis.providers_title'.tr(), + label: 'basis.providers', + title: 'basis.providers_title', ), RouteDestination( - route: const ServicesRoute(), + route: ServicesRoute(), icon: BrandIcons.box, - label: 'basis.services'.tr(), - title: 'basis.services'.tr(), + label: 'basis.services', + title: 'basis.services', ), RouteDestination( - route: const UsersRoute(), + route: UsersRoute(), icon: BrandIcons.users, - label: 'basis.users'.tr(), - title: 'basis.users'.tr(), + label: 'basis.users', + title: 'basis.users', ), RouteDestination( - route: const MoreRoute(), + route: MoreRoute(), icon: Icons.menu_rounded, - label: 'basis.more'.tr(), - title: 'basis.more'.tr(), + label: 'basis.more', + title: 'basis.more', ), ]; From 53ea69a000975e333a2dcf61e0cafcaeb1890acf Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 14:57:52 +0400 Subject: [PATCH 19/37] fix: minor code tweaks (no functional changes) --- lib/config/localization.dart | 7 ++++--- .../graphql_maps/graphql_api_map.dart | 3 ++- .../server_installation_repository.dart | 2 +- .../server_installation_state.dart | 2 +- lib/logic/get_it/api_config.dart | 3 +-- lib/theming/factory/app_theme_factory.dart | 3 ++- lib/ui/pages/users/new_user.dart | 21 +++++++++---------- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/config/localization.dart b/lib/config/localization.dart index 3f55fae2..64d734ee 100644 --- a/lib/config/localization.dart +++ b/lib/config/localization.dart @@ -3,11 +3,12 @@ import 'package:flutter/material.dart'; class Localization extends StatelessWidget { const Localization({ + required this.child, super.key, - this.child, }); - final Widget? child; + final Widget child; + @override Widget build(final BuildContext context) => EasyLocalization( supportedLocales: const [ @@ -37,6 +38,6 @@ class Localization extends StatelessWidget { useFallbackTranslations: true, saveLocale: false, useOnlyLangCode: false, - child: child!, + child: child, ); } diff --git a/lib/logic/api_maps/graphql_maps/graphql_api_map.dart b/lib/logic/api_maps/graphql_maps/graphql_api_map.dart index 698f59e8..8723e188 100644 --- a/lib/logic/api_maps/graphql_maps/graphql_api_map.dart +++ b/lib/logic/api_maps/graphql_maps/graphql_api_map.dart @@ -119,8 +119,9 @@ abstract class GraphQLApiMap { String token = ''; final serverDetails = getIt().serverDetails; if (serverDetails != null) { - token = getIt().serverDetails!.apiToken; + token = serverDetails.apiToken; } + return token; } diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 90035fea..75daa0ab 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -475,7 +475,7 @@ class ServerInstallationRepository { Future deleteServerDetails() async { await box.delete(BNames.serverDetails); - getIt().init(); + await getIt().init(); } Future saveServerProviderType(final ServerProviderType type) async { diff --git a/lib/logic/cubit/server_installation/server_installation_state.dart b/lib/logic/cubit/server_installation/server_installation_state.dart index e89e0c6b..8636ffee 100644 --- a/lib/logic/cubit/server_installation/server_installation_state.dart +++ b/lib/logic/cubit/server_installation/server_installation_state.dart @@ -50,7 +50,7 @@ abstract class ServerInstallationState extends Equatable { bool get isServerCreated => serverDetails != null; bool get isFullyInitialized => - _fulfillmentList.every((final el) => el == true); + _fulfillmentList.every((final el) => el ?? false); ServerSetupProgress get progress => ServerSetupProgress .values[_fulfillmentList.where((final el) => el!).length]; diff --git a/lib/logic/get_it/api_config.dart b/lib/logic/get_it/api_config.dart index b32cd995..21f1bb17 100644 --- a/lib/logic/get_it/api_config.dart +++ b/lib/logic/get_it/api_config.dart @@ -88,7 +88,6 @@ class ApiConfigModel { } void clear() { - _localeCode = null; _serverProviderKey = null; _dnsProvider = null; _serverLocation = null; @@ -101,7 +100,7 @@ class ApiConfigModel { _serverProvider = null; } - void init() { + Future init() async { _localeCode = 'en'; _serverProviderKey = _box.get(BNames.hetznerKey); _serverLocation = _box.get(BNames.serverLocation); diff --git a/lib/theming/factory/app_theme_factory.dart b/lib/theming/factory/app_theme_factory.dart index 4bb3f31b..e3ca53d9 100644 --- a/lib/theming/factory/app_theme_factory.dart +++ b/lib/theming/factory/app_theme_factory.dart @@ -50,7 +50,8 @@ abstract class AppThemeFactory { static Future _getDynamicColors(final Brightness brightness) { try { return DynamicColorPlugin.getCorePalette().then( - (final corePallet) => corePallet?.toColorScheme(brightness: brightness), + (final corePallete) => + corePallete?.toColorScheme(brightness: brightness), ); } on PlatformException { return Future.value(null); diff --git a/lib/ui/pages/users/new_user.dart b/lib/ui/pages/users/new_user.dart index a55fb681..3327da33 100644 --- a/lib/ui/pages/users/new_user.dart +++ b/lib/ui/pages/users/new_user.dart @@ -14,17 +14,16 @@ class NewUserPage extends StatelessWidget { return BlocProvider( create: (final BuildContext context) { final jobCubit = context.read(); - final jobState = jobCubit.state; - final users = []; - users.addAll(context.read().state.users); - if (jobState is JobsStateWithJobs) { - final jobs = jobState.clientJobList; - for (final job in jobs) { - if (job is CreateUserJob) { - users.add(job.user); - } - } - } + + // final jobsState = jobCubit.state; + // final users = [ + // ...context.read().state.users, + // if (jobsState is JobsStateWithJobs) + // ...jobsState.clientJobList + // .whereType() + // .map((final job) => job.user), + // ]; + return UserFormCubit( jobsCubit: jobCubit, fieldFactory: FieldCubitFactory(context), From 0ad15061a3f6d131990a8cdcc15ff5bf0353fb8e Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 15:04:32 +0400 Subject: [PATCH 20/37] chore: updated api codegen code --- .../json/digital_ocean_server_info.g.dart | 10 ++++++---- .../dns_providers/cloudflare_dns_info.g.dart | 4 ++-- .../json/dns_providers/desec_dns_info.g.dart | 4 ++-- .../digital_ocean_dns_info.g.dart | 8 ++++---- .../models/json/hetzner_server_info.g.dart | 18 ++++++++++-------- .../models/json/recovery_token_status.g.dart | 2 +- lib/logic/models/json/server_job.g.dart | 2 +- 7 files changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/logic/models/json/digital_ocean_server_info.g.dart b/lib/logic/models/json/digital_ocean_server_info.g.dart index 9610dbce..ea3d73d9 100644 --- a/lib/logic/models/json/digital_ocean_server_info.g.dart +++ b/lib/logic/models/json/digital_ocean_server_info.g.dart @@ -10,8 +10,10 @@ DigitalOceanVolume _$DigitalOceanVolumeFromJson(Map json) => DigitalOceanVolume( json['id'] as String, json['name'] as String, - json['size_gigabytes'] as int, - (json['droplet_ids'] as List?)?.map((e) => e as int).toList(), + (json['size_gigabytes'] as num).toInt(), + (json['droplet_ids'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), ); Map _$DigitalOceanVolumeToJson(DigitalOceanVolume instance) => @@ -42,10 +44,10 @@ DigitalOceanServerType _$DigitalOceanServerTypeFromJson( (json['regions'] as List).map((e) => e as String).toList(), (json['memory'] as num).toDouble(), json['description'] as String, - json['disk'] as int, + (json['disk'] as num).toInt(), (json['price_monthly'] as num).toDouble(), json['slug'] as String, - json['vcpus'] as int, + (json['vcpus'] as num).toInt(), ); Map _$DigitalOceanServerTypeToJson( diff --git a/lib/logic/models/json/dns_providers/cloudflare_dns_info.g.dart b/lib/logic/models/json/dns_providers/cloudflare_dns_info.g.dart index d02fc2d2..03b871dc 100644 --- a/lib/logic/models/json/dns_providers/cloudflare_dns_info.g.dart +++ b/lib/logic/models/json/dns_providers/cloudflare_dns_info.g.dart @@ -24,8 +24,8 @@ CloudflareDnsRecord _$CloudflareDnsRecordFromJson(Map json) => name: json['name'] as String?, content: json['content'] as String?, zoneName: json['zone_name'] as String, - ttl: json['ttl'] as int? ?? 3600, - priority: json['priority'] as int? ?? 10, + ttl: (json['ttl'] as num?)?.toInt() ?? 3600, + priority: (json['priority'] as num?)?.toInt() ?? 10, id: json['id'] as String?, ); diff --git a/lib/logic/models/json/dns_providers/desec_dns_info.g.dart b/lib/logic/models/json/dns_providers/desec_dns_info.g.dart index bfb9126e..5c2415ee 100644 --- a/lib/logic/models/json/dns_providers/desec_dns_info.g.dart +++ b/lib/logic/models/json/dns_providers/desec_dns_info.g.dart @@ -8,7 +8,7 @@ part of 'desec_dns_info.dart'; DesecDomain _$DesecDomainFromJson(Map json) => DesecDomain( name: json['name'] as String, - minimumTtl: json['minimum_ttl'] as int?, + minimumTtl: (json['minimum_ttl'] as num?)?.toInt(), ); Map _$DesecDomainToJson(DesecDomain instance) => @@ -21,7 +21,7 @@ DesecDnsRecord _$DesecDnsRecordFromJson(Map json) => DesecDnsRecord( subname: json['subname'] as String, type: json['type'] as String, - ttl: json['ttl'] as int, + ttl: (json['ttl'] as num).toInt(), records: (json['records'] as List).map((e) => e as String).toList(), ); diff --git a/lib/logic/models/json/dns_providers/digital_ocean_dns_info.g.dart b/lib/logic/models/json/dns_providers/digital_ocean_dns_info.g.dart index d66c0352..df419647 100644 --- a/lib/logic/models/json/dns_providers/digital_ocean_dns_info.g.dart +++ b/lib/logic/models/json/dns_providers/digital_ocean_dns_info.g.dart @@ -9,7 +9,7 @@ part of 'digital_ocean_dns_info.dart'; DigitalOceanDomain _$DigitalOceanDomainFromJson(Map json) => DigitalOceanDomain( name: json['name'] as String, - ttl: json['ttl'] as int?, + ttl: (json['ttl'] as num?)?.toInt(), ); Map _$DigitalOceanDomainToJson(DigitalOceanDomain instance) => @@ -21,12 +21,12 @@ Map _$DigitalOceanDomainToJson(DigitalOceanDomain instance) => DigitalOceanDnsRecord _$DigitalOceanDnsRecordFromJson( Map json) => DigitalOceanDnsRecord( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), name: json['name'] as String, type: json['type'] as String, - ttl: json['ttl'] as int, + ttl: (json['ttl'] as num).toInt(), data: json['data'] as String, - priority: json['priority'] as int?, + priority: (json['priority'] as num?)?.toInt(), ); Map _$DigitalOceanDnsRecordToJson( diff --git a/lib/logic/models/json/hetzner_server_info.g.dart b/lib/logic/models/json/hetzner_server_info.g.dart index 27b94deb..fb877295 100644 --- a/lib/logic/models/json/hetzner_server_info.g.dart +++ b/lib/logic/models/json/hetzner_server_info.g.dart @@ -8,7 +8,7 @@ part of 'hetzner_server_info.dart'; HetznerServerInfo _$HetznerServerInfoFromJson(Map json) => HetznerServerInfo( - json['id'] as int, + (json['id'] as num).toInt(), json['name'] as String, $enumDecode(_$ServerStatusEnumMap, json['status']), DateTime.parse(json['created'] as String), @@ -16,7 +16,9 @@ HetznerServerInfo _$HetznerServerInfoFromJson(Map json) => json['server_type'] as Map), HetznerServerInfo.locationFromJson(json['datacenter'] as Map), HetznerPublicNetInfo.fromJson(json['public_net'] as Map), - (json['volumes'] as List).map((e) => e as int).toList(), + (json['volumes'] as List) + .map((e) => (e as num).toInt()) + .toList(), ); Map _$HetznerServerInfoToJson(HetznerServerInfo instance) => @@ -58,7 +60,7 @@ Map _$HetznerPublicNetInfoToJson( }; HetznerIp4 _$HetznerIp4FromJson(Map json) => HetznerIp4( - json['id'] as int, + (json['id'] as num).toInt(), json['ip'] as String, json['blocked'] as bool, json['dns_ptr'] as String, @@ -75,9 +77,9 @@ Map _$HetznerIp4ToJson(HetznerIp4 instance) => HetznerServerTypeInfo _$HetznerServerTypeInfoFromJson( Map json) => HetznerServerTypeInfo( - json['cores'] as int, + (json['cores'] as num).toInt(), json['memory'] as num, - json['disk'] as int, + (json['disk'] as num).toInt(), (json['prices'] as List) .map((e) => HetznerPriceInfo.fromJson(e as Map)) .toList(), @@ -132,9 +134,9 @@ Map _$HetznerLocationToJson(HetznerLocation instance) => HetznerVolume _$HetznerVolumeFromJson(Map json) => HetznerVolume( - json['id'] as int, - json['size'] as int, - json['serverId'] as int?, + (json['id'] as num).toInt(), + (json['size'] as num).toInt(), + (json['serverId'] as num?)?.toInt(), json['name'] as String, json['linux_device'] as String?, ); diff --git a/lib/logic/models/json/recovery_token_status.g.dart b/lib/logic/models/json/recovery_token_status.g.dart index 9fb6d6cd..eda00a4a 100644 --- a/lib/logic/models/json/recovery_token_status.g.dart +++ b/lib/logic/models/json/recovery_token_status.g.dart @@ -15,7 +15,7 @@ RecoveryKeyStatus _$RecoveryKeyStatusFromJson(Map json) => expiration: json['expiration'] == null ? null : DateTime.parse(json['expiration'] as String), - usesLeft: json['uses_left'] as int?, + usesLeft: (json['uses_left'] as num?)?.toInt(), ); Map _$RecoveryKeyStatusToJson(RecoveryKeyStatus instance) => diff --git a/lib/logic/models/json/server_job.g.dart b/lib/logic/models/json/server_job.g.dart index 712c086f..b2136abb 100644 --- a/lib/logic/models/json/server_job.g.dart +++ b/lib/logic/models/json/server_job.g.dart @@ -15,7 +15,7 @@ ServerJob _$ServerJobFromJson(Map json) => ServerJob( updatedAt: DateTime.parse(json['updatedAt'] as String), createdAt: DateTime.parse(json['createdAt'] as String), error: json['error'] as String?, - progress: json['progress'] as int?, + progress: (json['progress'] as num?)?.toInt(), result: json['result'] as String?, statusText: json['statusText'] as String?, finishedAt: json['finishedAt'] == null From ea2cc28ac9194e351adae69ae8134375e33d59fc Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 19:36:00 +0400 Subject: [PATCH 21/37] feat: introduced app_controller, rehooked dependencies from app_settings_cubit, added language picker to settings_page --- lib/config/app_controller/app_controller.dart | 183 ++++++++++++++++++ .../inherited_app_controller.dart | 106 ++++++++++ lib/config/bloc_config.dart | 97 ++++------ lib/config/hive_config.dart | 11 +- .../datasources/preferences_datasource.dart | 36 ++++ .../preferences_hive_datasource.dart | 39 ++++ .../inherited_preferences_repository.dart | 63 ++++++ .../preferences_repository.dart | 73 +++++++ .../app_settings/app_settings_cubit.dart | 66 ------- .../app_settings/app_settings_state.dart | 35 ---- lib/main.dart | 165 +++++++++------- .../pages/more/app_settings/app_settings.dart | 110 ++++------- .../more/app_settings/developer_settings.dart | 14 +- .../more/app_settings/language_picker.dart | 53 +++++ .../more/app_settings/reset_app_button.dart | 42 ++++ .../pages/more/app_settings/theme_picker.dart | 44 +++++ lib/ui/pages/onboarding/onboarding.dart | 5 +- lib/ui/pages/root_route.dart | 116 ++--------- .../initializing/server_type_picker.dart | 8 +- 19 files changed, 840 insertions(+), 426 deletions(-) create mode 100644 lib/config/app_controller/app_controller.dart create mode 100644 lib/config/app_controller/inherited_app_controller.dart create mode 100644 lib/config/preferences_repository/datasources/preferences_datasource.dart create mode 100644 lib/config/preferences_repository/datasources/preferences_hive_datasource.dart create mode 100644 lib/config/preferences_repository/inherited_preferences_repository.dart create mode 100644 lib/config/preferences_repository/preferences_repository.dart delete mode 100644 lib/logic/cubit/app_settings/app_settings_cubit.dart delete mode 100644 lib/logic/cubit/app_settings/app_settings_state.dart create mode 100644 lib/ui/pages/more/app_settings/language_picker.dart create mode 100644 lib/ui/pages/more/app_settings/reset_app_button.dart create mode 100644 lib/ui/pages/more/app_settings/theme_picker.dart diff --git a/lib/config/app_controller/app_controller.dart b/lib/config/app_controller/app_controller.dart new file mode 100644 index 00000000..a14eaa19 --- /dev/null +++ b/lib/config/app_controller/app_controller.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:material_color_utilities/material_color_utilities.dart' + as color_utils; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart'; + +/// A class that many Widgets can interact with to read current app +/// configuration, update it, or listen to its changes. +/// +/// AppController uses repo to change persistent data. +class AppController with ChangeNotifier { + AppController(this._repo); + + /// repo encapsulates retrieval and storage of preferences + final PreferencesRepository _repo; + + /// TODO: to be removed or changed + late final ApiConfigModel _apiConfigModel = getIt.get(); + + bool _loaded = false; + bool get loaded => _loaded; + + // localization + late Locale _locale; + Locale get locale => _locale; + late List _supportedLocales; + List get supportedLocales => _supportedLocales; + + // theme + late ThemeData _lightTheme; + ThemeData get lightTheme => _lightTheme; + late ThemeData _darkTheme; + ThemeData get darkTheme => _darkTheme; + late color_utils.CorePalette _corePalette; + color_utils.CorePalette get corePalette => _corePalette; + + late bool _systemThemeModeActive; + bool get systemThemeModeActive => _systemThemeModeActive; + + late bool _darkThemeModeActive; + bool get darkThemeModeActive => _darkThemeModeActive; + + ThemeMode get themeMode => systemThemeModeActive + ? ThemeMode.system + : darkThemeModeActive + ? ThemeMode.dark + : ThemeMode.light; + // // Make ThemeMode a private variable so it is not updated directly without + // // also persisting the changes with the repo.. + // late ThemeMode _themeMode; + // // Allow Widgets to read the user's preferred ThemeMode. + // ThemeMode get themeMode => _themeMode; + + late bool _shouldShowOnboarding; + bool get shouldShowOnboarding => _shouldShowOnboarding; + + /// Load the user's settings from the SettingsService. It may load from a + /// local database or the internet. The controller only knows it can load the + /// settings from the service. + Future init({ + // required final AppPreferencesRepository repo, + required final ThemeData lightThemeData, + required final ThemeData darkThemeData, + required final color_utils.CorePalette colorPalette, + }) async { + // _repo = repo; + + await Future.wait([ + // load locale + () async { + _supportedLocales = await _repo.getSupportedLocales(); + + _locale = await _repo.getActiveLocale(); + // preset value to other state holders + await _apiConfigModel.setLocaleCode(_locale.languageCode); + await _repo.setDelegateLocale(_locale); + }(), + + // load theme mode && initialize theme + () async { + _lightTheme = lightThemeData; + _darkTheme = darkThemeData; + _corePalette = colorPalette; + // _themeMode = await _repo.getThemeMode(); + _darkThemeModeActive = await _repo.getDarkThemeModeFlag(); + _systemThemeModeActive = await _repo.getSystemThemeModeFlag(); + }(), + + // load onboarding flag + () async { + _shouldShowOnboarding = await _repo.getShouldShowOnboarding(); + }(), + ]); + + _loaded = true; + // Important! Inform listeners a change has occurred. + notifyListeners(); + } + + // updateRepoReference + + Future setShouldShowOnboarding(final bool newValue) async { + // Do not perform any work if new and old flag values are identical + if (newValue == shouldShowOnboarding) { + return; + } + + // Store the flag in memory + _shouldShowOnboarding = newValue; + + notifyListeners(); + + // Persist the change + await _repo.setShouldShowOnboarding(newValue); + } + + Future setSystemThemeModeFlag(final bool newValue) async { + // Do not perform any work if new and old ThemeMode are identical + if (systemThemeModeActive == newValue) { + return; + } + + // Store the new ThemeMode in memory + _systemThemeModeActive = newValue; + + // Inform listeners a change has occurred. + notifyListeners(); + + // Persist the change + await _repo.setSystemModeFlag(newValue); + } + + Future setDarkThemeModeFlag(final bool newValue) async { + // Do not perform any work if new and old ThemeMode are identical + if (darkThemeModeActive == newValue) { + return; + } + + // Store the new ThemeMode in memory + _darkThemeModeActive = newValue; + + // Inform listeners a change has occurred. + notifyListeners(); + + // Persist the change + await _repo.setDarkThemeModeFlag(newValue); + } + + // /// Update and persist the ThemeMode based on the user's selection. + // Future setThemeMode(final ThemeMode newThemeMode) async { + // // Do not perform any work if new and old ThemeMode are identical + // if (newThemeMode == themeMode) { + // return; + // } + + // // Store the new ThemeMode in memory + // _themeMode = newThemeMode; + + // // Inform listeners a change has occurred. + // notifyListeners(); + + // // Persist the change + // await _repo.setThemeMode(newThemeMode); + // } + + Future setLocale(final Locale newLocale) async { + // Do not perform any work if new and old Locales are identical + if (newLocale == _locale) { + return; + } + + // Store the new Locale in memory + _locale = newLocale; + + /// update locale delegate, which in turn should update deps + await _repo.setDelegateLocale(newLocale); + + // Persist the change + await _repo.setActiveLocale(newLocale); + // Update other locale holders + await _apiConfigModel.setLocaleCode(newLocale.languageCode); + } +} diff --git a/lib/config/app_controller/inherited_app_controller.dart b/lib/config/app_controller/inherited_app_controller.dart new file mode 100644 index 00000000..e7eeac7b --- /dev/null +++ b/lib/config/app_controller/inherited_app_controller.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:material_color_utilities/material_color_utilities.dart' + as color_utils; +import 'package:selfprivacy/config/app_controller/app_controller.dart'; +import 'package:selfprivacy/config/brand_colors.dart'; +import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart'; +import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart'; +import 'package:selfprivacy/theming/factory/app_theme_factory.dart'; + +class _AppControllerInjector extends InheritedNotifier { + const _AppControllerInjector({ + required super.child, + required super.notifier, + }); +} + +class InheritedAppController extends StatefulWidget { + const InheritedAppController({ + required this.child, + super.key, + }); + + final Widget child; + + @override + State createState() => _InheritedAppControllerState(); + + static AppController of(final BuildContext context) => context + .dependOnInheritedWidgetOfExactType<_AppControllerInjector>()! + .notifier!; +} + +class _InheritedAppControllerState extends State { + // actual state provider + late AppController controller; + // hold local reference to active repo + late PreferencesRepository _repo; + + bool initTriggerred = false; + + @override + void didChangeDependencies() { + /// update reference on dependency change + _repo = InheritedPreferencesRepository.of(context)!; + + if (!initTriggerred) { + /// hook controller repo to local reference + controller = AppController(_repo); + initialize(); + initTriggerred = true; + } + + super.didChangeDependencies(); + } + + Future initialize() async { + late final ThemeData lightThemeData; + late final ThemeData darkThemeData; + late final color_utils.CorePalette colorPalette; + + await Future.wait( + >[ + () async { + lightThemeData = await AppThemeFactory.create( + isDark: false, + fallbackColor: BrandColors.primary, + ); + }(), + () async { + darkThemeData = await AppThemeFactory.create( + isDark: true, + fallbackColor: BrandColors.primary, + ); + }(), + () async { + colorPalette = (await AppThemeFactory.getCorePalette()) ?? + color_utils.CorePalette.of(BrandColors.primary.value); + }(), + ], + ); + + await controller.init( + colorPalette: colorPalette, + lightThemeData: lightThemeData, + darkThemeData: darkThemeData, + ); + + WidgetsBinding.instance.addPostFrameCallback((final _) { + if (mounted) { + setState(() {}); + } + }); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(final BuildContext context) => _AppControllerInjector( + notifier: controller, + child: widget.child, + ); +} diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 2a42456c..83027d7b 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -8,7 +8,6 @@ import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/bloc/users/users_bloc.dart'; import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; -import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; @@ -56,58 +55,46 @@ class BlocAndProviderConfigState extends State { } @override - Widget build(final BuildContext context) { - const isDark = false; - const isAutoDark = true; - - return MultiProvider( - providers: [ - BlocProvider( - create: (final _) => AppSettingsCubit( - isDarkModeOn: isDark, - isAutoDarkModeOn: isAutoDark, - isOnboardingShowing: true, - )..load(), - ), - BlocProvider( - create: (final _) => supportSystemCubit, - ), - BlocProvider( - create: (final _) => serverInstallationCubit, - lazy: false, - ), - BlocProvider( - create: (final _) => usersBloc, - lazy: false, - ), - BlocProvider( - create: (final _) => servicesBloc, - ), - BlocProvider( - create: (final _) => backupsBloc, - ), - BlocProvider( - create: (final _) => dnsRecordsCubit, - ), - BlocProvider( - create: (final _) => recoveryKeyBloc, - ), - BlocProvider( - create: (final _) => devicesBloc, - ), - BlocProvider( - create: (final _) => serverJobsBloc, - ), - BlocProvider(create: (final _) => connectionStatusBloc), - BlocProvider( - create: (final _) => serverDetailsCubit, - ), - BlocProvider(create: (final _) => volumesBloc), - BlocProvider( - create: (final _) => JobsCubit(), - ), - ], - child: widget.child, - ); - } + Widget build(final BuildContext context) => MultiProvider( + providers: [ + BlocProvider( + create: (final _) => supportSystemCubit, + ), + BlocProvider( + create: (final _) => serverInstallationCubit, + lazy: false, + ), + BlocProvider( + create: (final _) => usersBloc, + lazy: false, + ), + BlocProvider( + create: (final _) => servicesBloc, + ), + BlocProvider( + create: (final _) => backupsBloc, + ), + BlocProvider( + create: (final _) => dnsRecordsCubit, + ), + BlocProvider( + create: (final _) => recoveryKeyBloc, + ), + BlocProvider( + create: (final _) => devicesBloc, + ), + BlocProvider( + create: (final _) => serverJobsBloc, + ), + BlocProvider(create: (final _) => connectionStatusBloc), + BlocProvider( + create: (final _) => serverDetailsCubit, + ), + BlocProvider(create: (final _) => volumesBloc), + BlocProvider( + create: (final _) => JobsCubit(), + ), + ], + child: widget.child, + ); } diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index 55b35e9e..11672859 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -60,17 +60,20 @@ class HiveConfig { /// Mappings for the different boxes and their keys class BNames { - /// App settings box. Contains app settings like [isDarkModeOn], [isOnboardingShowing] + /// App settings box. Contains app settings like [darkThemeModeOn], [shouldShowOnboarding] static String appSettingsBox = 'appSettings'; /// A boolean field of [appSettingsBox] box. - static String isDarkModeOn = 'isDarkModeOn'; + static String darkThemeModeOn = 'isDarkModeOn'; /// A boolean field of [appSettingsBox] box. - static String isAutoDarkModeOn = 'isAutoDarkModeOn'; + static String systemThemeModeOn = 'isAutoDarkModeOn'; /// A boolean field of [appSettingsBox] box. - static String isOnboardingShowing = 'isOnboardingShowing'; + static String shouldShowOnboarding = 'isOnboardingShowing'; + + /// A string field + static String appLocale = 'appLocale'; /// Encryption key to decrypt [serverInstallationBox] and [usersBox] box. static String serverInstallationEncryptionKey = 'key'; diff --git a/lib/config/preferences_repository/datasources/preferences_datasource.dart b/lib/config/preferences_repository/datasources/preferences_datasource.dart new file mode 100644 index 00000000..53ef8b09 --- /dev/null +++ b/lib/config/preferences_repository/datasources/preferences_datasource.dart @@ -0,0 +1,36 @@ +/// abstraction for manipulation of stored app preferences +abstract class PreferencesDataSource { + /// should onboarding be shown + Future getOnboardingFlag(); + + /// should onboarding be shown + Future setOnboardingFlag(final bool newValue); + + // TODO: should probably deprecate the following, instead add the + // getThemeMode and setThemeMode methods, which store one value instead of + // flags. + + /// should system theme mode be enabled + Future getSystemThemeModeFlag(); + + /// should system theme mode be enabled + Future setSystemThemeModeFlag(final bool newValue); + + /// should dark theme be enabled + Future getDarkThemeModeFlag(); + + /// should dark theme be enabled + Future setDarkThemeModeFlag(final bool newValue); + + /// locale, as set by user + /// + /// + /// when null, app takes system locale + Future getLocale(); + + /// locale, as set by user + /// + /// + /// when null, app takes system locale + Future setLocale(final String newLocale); +} diff --git a/lib/config/preferences_repository/datasources/preferences_hive_datasource.dart b/lib/config/preferences_repository/datasources/preferences_hive_datasource.dart new file mode 100644 index 00000000..80dd9f11 --- /dev/null +++ b/lib/config/preferences_repository/datasources/preferences_hive_datasource.dart @@ -0,0 +1,39 @@ +import 'package:hive/hive.dart'; +import 'package:selfprivacy/config/hive_config.dart'; +import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart'; + +/// app preferences data source hive implementation +class PreferencesHiveDataSource implements PreferencesDataSource { + final Box _appSettingsBox = Hive.box(BNames.appSettingsBox); + + @override + Future getOnboardingFlag() async => + _appSettingsBox.get(BNames.shouldShowOnboarding, defaultValue: true); + + @override + Future setOnboardingFlag(final bool newValue) async => + _appSettingsBox.put(BNames.shouldShowOnboarding, newValue); + + @override + Future getSystemThemeModeFlag() async => + _appSettingsBox.get(BNames.systemThemeModeOn); + + @override + Future setSystemThemeModeFlag(final bool newValue) async => + _appSettingsBox.put(BNames.systemThemeModeOn, newValue); + + @override + Future getDarkThemeModeFlag() async => + _appSettingsBox.get(BNames.darkThemeModeOn); + + @override + Future setDarkThemeModeFlag(final bool newValue) async => + _appSettingsBox.put(BNames.darkThemeModeOn, newValue); + + @override + Future getLocale() async => _appSettingsBox.get(BNames.appLocale); + + @override + Future setLocale(final String newLocale) async => + _appSettingsBox.put(BNames.appLocale, newLocale); +} diff --git a/lib/config/preferences_repository/inherited_preferences_repository.dart b/lib/config/preferences_repository/inherited_preferences_repository.dart new file mode 100644 index 00000000..4a2881b1 --- /dev/null +++ b/lib/config/preferences_repository/inherited_preferences_repository.dart @@ -0,0 +1,63 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart'; +import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart'; + +class _PreferencesRepositoryInjector extends InheritedWidget { + const _PreferencesRepositoryInjector({ + required this.settingsRepository, + required super.child, + }); + + final PreferencesRepository settingsRepository; + + @override + bool updateShouldNotify( + covariant final _PreferencesRepositoryInjector oldWidget, + ) => + oldWidget.settingsRepository != settingsRepository; +} + +/// Creates and injects app preferences repository inside widget tree. +class InheritedPreferencesRepository extends StatefulWidget { + const InheritedPreferencesRepository({ + required this.child, + required this.dataSource, + super.key, + }); + + final PreferencesDataSource dataSource; + final Widget child; + + @override + State createState() => + _InheritedPreferencesRepositoryState(); + + static PreferencesRepository? of(final BuildContext context) => context + .dependOnInheritedWidgetOfExactType<_PreferencesRepositoryInjector>() + ?.settingsRepository; +} + +class _InheritedPreferencesRepositoryState + extends State { + late PreferencesRepository repo; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + /// recreate repo each time dependencies change + repo = PreferencesRepository( + dataSource: widget.dataSource, + setDelegateLocale: EasyLocalization.of(context)!.setLocale, + getDelegateLocale: () => EasyLocalization.of(context)!.locale, + getSupportedLocales: () => EasyLocalization.of(context)!.supportedLocales, + ); + } + + @override + Widget build(final BuildContext context) => _PreferencesRepositoryInjector( + settingsRepository: repo, + child: widget.child, + ); +} diff --git a/lib/config/preferences_repository/preferences_repository.dart b/lib/config/preferences_repository/preferences_repository.dart new file mode 100644 index 00000000..b649d38a --- /dev/null +++ b/lib/config/preferences_repository/preferences_repository.dart @@ -0,0 +1,73 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart'; + +class PreferencesRepository { + const PreferencesRepository({ + required this.dataSource, + required this.getSupportedLocales, + required this.getDelegateLocale, + required this.setDelegateLocale, + }); + + final PreferencesDataSource dataSource; + + /// easy localizations don't expose type of localization provider, + /// so it needs to be this crutchy (I could've created one more class-wrapper, + /// containing needed functions, but perceive it as boilerplate, because we + /// don't need additional encapsulation level here) + final FutureOr Function(Locale) setDelegateLocale; + final FutureOr> Function() getSupportedLocales; + final FutureOr Function() getDelegateLocale; + + Future getSystemThemeModeFlag() async => + (await dataSource.getSystemThemeModeFlag()) ?? true; + + Future setSystemThemeModeFlag(final bool newValue) async => + dataSource.setSystemThemeModeFlag(newValue); + + Future getDarkThemeModeFlag() async => + (await dataSource.getDarkThemeModeFlag()) ?? false; + + Future setDarkThemeModeFlag(final bool newValue) async => + dataSource.setDarkThemeModeFlag(newValue); + + Future setSystemModeFlag(final bool newValue) async => + dataSource.setSystemThemeModeFlag(newValue); + + // Future getThemeMode() async { + // final themeMode = await dataSource.getThemeMode()?? ThemeMode.system; + // } + // + // Future setThemeMode(final ThemeMode newThemeMode) => + // dataSource.setThemeMode(newThemeMode); + + Future> supportedLocales() async => getSupportedLocales(); + + Future getActiveLocale() async { + Locale? chosenLocale; + + final String? storedLocaleCode = await dataSource.getLocale(); + if (storedLocaleCode != null) { + chosenLocale = Locale(storedLocaleCode); + } + + // when it's null fallback on delegate locale + chosenLocale ??= await getDelegateLocale(); + + return chosenLocale; + } + + Future setActiveLocale(final Locale newLocale) async { + await dataSource.setLocale(newLocale.toString()); + } + + /// true when we need to show onboarding + Future getShouldShowOnboarding() async => + dataSource.getOnboardingFlag(); + + /// true when we need to show onboarding + Future setShouldShowOnboarding(final bool newValue) => + dataSource.setOnboardingFlag(newValue); +} diff --git a/lib/logic/cubit/app_settings/app_settings_cubit.dart b/lib/logic/cubit/app_settings/app_settings_cubit.dart deleted file mode 100644 index 548ed812..00000000 --- a/lib/logic/cubit/app_settings/app_settings_cubit.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hive/hive.dart'; -import 'package:material_color_utilities/material_color_utilities.dart' - as color_utils; -import 'package:selfprivacy/config/brand_colors.dart'; -import 'package:selfprivacy/config/hive_config.dart'; -import 'package:selfprivacy/theming/factory/app_theme_factory.dart'; - -export 'package:provider/provider.dart'; - -part 'app_settings_state.dart'; - -class AppSettingsCubit extends Cubit { - AppSettingsCubit({ - required final bool isDarkModeOn, - required final bool isAutoDarkModeOn, - required final bool isOnboardingShowing, - }) : super( - AppSettingsState( - isDarkModeOn: isDarkModeOn, - isAutoDarkModeOn: isAutoDarkModeOn, - isOnboardingShowing: isOnboardingShowing, - ), - ); - - Box box = Hive.box(BNames.appSettingsBox); - - void load() async { - final bool? isDarkModeOn = box.get(BNames.isDarkModeOn); - final bool? isAutoDarkModeOn = box.get(BNames.isAutoDarkModeOn); - final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing); - emit( - state.copyWith( - isDarkModeOn: isDarkModeOn, - isAutoDarkModeOn: isAutoDarkModeOn, - isOnboardingShowing: isOnboardingShowing, - ), - ); - WidgetsFlutterBinding.ensureInitialized(); - final color_utils.CorePalette? colorPalette = - await AppThemeFactory.getCorePalette(); - emit( - state.copyWith( - corePalette: colorPalette, - ), - ); - } - - void updateDarkMode({required final bool isDarkModeOn}) { - box.put(BNames.isDarkModeOn, isDarkModeOn); - emit(state.copyWith(isDarkModeOn: isDarkModeOn)); - } - - void updateAutoDarkMode({required final bool isAutoDarkModeOn}) { - box.put(BNames.isAutoDarkModeOn, isAutoDarkModeOn); - emit(state.copyWith(isAutoDarkModeOn: isAutoDarkModeOn)); - } - - void turnOffOnboarding({final bool isOnboardingShowing = false}) { - box.put(BNames.isOnboardingShowing, isOnboardingShowing); - - emit(state.copyWith(isOnboardingShowing: isOnboardingShowing)); - } -} diff --git a/lib/logic/cubit/app_settings/app_settings_state.dart b/lib/logic/cubit/app_settings/app_settings_state.dart deleted file mode 100644 index ad364d66..00000000 --- a/lib/logic/cubit/app_settings/app_settings_state.dart +++ /dev/null @@ -1,35 +0,0 @@ -part of 'app_settings_cubit.dart'; - -class AppSettingsState extends Equatable { - const AppSettingsState({ - required this.isDarkModeOn, - required this.isAutoDarkModeOn, - required this.isOnboardingShowing, - this.corePalette, - }); - - final bool isDarkModeOn; - final bool isAutoDarkModeOn; - final bool isOnboardingShowing; - final color_utils.CorePalette? corePalette; - - AppSettingsState copyWith({ - final bool? isDarkModeOn, - final bool? isAutoDarkModeOn, - final bool? isOnboardingShowing, - final color_utils.CorePalette? corePalette, - }) => - AppSettingsState( - isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn, - isAutoDarkModeOn: isAutoDarkModeOn ?? this.isAutoDarkModeOn, - isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing, - corePalette: corePalette ?? this.corePalette, - ); - - color_utils.CorePalette get corePaletteOrDefault => - corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value); - - @override - List get props => - [isDarkModeOn, isAutoDarkModeOn, isOnboardingShowing, corePalette]; -} diff --git a/lib/main.dart b/lib/main.dart index 4eb1933c..9acae644 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,21 +1,19 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart'; import 'package:selfprivacy/config/bloc_config.dart'; import 'package:selfprivacy/config/bloc_observer.dart'; import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/config/localization.dart'; -import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; -import 'package:selfprivacy/theming/factory/app_theme_factory.dart'; +import 'package:selfprivacy/config/preferences_repository/datasources/preferences_hive_datasource.dart'; +import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart'; import 'package:selfprivacy/ui/router/router.dart'; -// import 'package:wakelock/wakelock.dart'; import 'package:timezone/data/latest.dart' as tz; void main() async { - WidgetsFlutterBinding.ensureInitialized(); - await HiveConfig.init(); // await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); // try { @@ -26,86 +24,111 @@ void main() async { // print(e); // } + await Future.wait( + >[ + HiveConfig.init(), + EasyLocalization.ensureInitialized(), + ], + ); await getItSetup(); - await EasyLocalization.ensureInitialized(); - tz.initializeTimeZones(); - final ThemeData lightThemeData = await AppThemeFactory.create( - isDark: false, - fallbackColor: BrandColors.primary, - ); - final ThemeData darkThemeData = await AppThemeFactory.create( - isDark: true, - fallbackColor: BrandColors.primary, - ); + tz.initializeTimeZones(); Bloc.observer = SimpleBlocObserver(); runApp( - SelfprivacyApp( - lightThemeData: lightThemeData, - darkThemeData: darkThemeData, + Localization( + child: InheritedPreferencesRepository( + dataSource: PreferencesHiveDataSource(), + child: const InheritedAppController( + child: AppBuilder(), + ), + ), ), ); } -class SelfprivacyApp extends StatelessWidget { - SelfprivacyApp({ - required this.lightThemeData, - required this.darkThemeData, - super.key, - }); - - final ThemeData lightThemeData; - final ThemeData darkThemeData; - - final _appRouter = RootRouter(getIt.get().navigatorKey); +class AppBuilder extends StatelessWidget { + const AppBuilder({super.key}); @override - Widget build(final BuildContext context) => Localization( - child: BlocAndProviderConfig( - child: BlocBuilder( - builder: ( - final BuildContext context, - final AppSettingsState appSettings, - ) { - getIt.get().setLocaleCode( - context.locale.languageCode, - ); - return MaterialApp.router( - routeInformationParser: _appRouter.defaultRouteParser(), - routerDelegate: _appRouter.delegate(), - scaffoldMessengerKey: - getIt.get().scaffoldMessengerKey, - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, - title: 'SelfPrivacy', - theme: lightThemeData, - darkTheme: darkThemeData, - themeMode: appSettings.isAutoDarkModeOn - ? ThemeMode.system - : appSettings.isDarkModeOn - ? ThemeMode.dark - : ThemeMode.light, - scrollBehavior: const MaterialScrollBehavior().copyWith( - scrollbars: false, - ), - builder: (final BuildContext context, final Widget? widget) { - Widget error = - const Center(child: Text('...rendering error...')); - if (widget is Scaffold || widget is Navigator) { - error = Scaffold(body: error); - } - ErrorWidget.builder = - (final FlutterErrorDetails errorDetails) => error; + Widget build(final BuildContext context) { + final appController = InheritedAppController.of(context); - return widget ?? error; - }, - ); - }, + if (appController.loaded) { + return const SelfprivacyApp(); + } + + return const SplashScreen(); + } +} + +/// Widget to be shown +/// until essential app initialization is completed +class SplashScreen extends StatelessWidget { + const SplashScreen({super.key}); + + @override + Widget build(final BuildContext context) => const ColoredBox( + color: Colors.white, + child: Center( + child: CircularProgressIndicator.adaptive( + valueColor: AlwaysStoppedAnimation(BrandColors.primary), ), ), ); } + +class SelfprivacyApp extends StatefulWidget { + const SelfprivacyApp({ + super.key, + }); + + @override + State createState() => _SelfprivacyAppState(); +} + +class _SelfprivacyAppState extends State { + final appKey = UniqueKey(); + final _appRouter = RootRouter(getIt.get().navigatorKey); + + @override + Widget build(final BuildContext context) { + final appController = InheritedAppController.of(context); + + return BlocAndProviderConfig( + child: MaterialApp.router( + key: appKey, + title: 'SelfPrivacy', + // routing + routeInformationParser: _appRouter.defaultRouteParser(), + routerDelegate: _appRouter.delegate(), + scaffoldMessengerKey: + getIt.get().scaffoldMessengerKey, + // localization settings + locale: context.locale, + supportedLocales: context.supportedLocales, + localizationsDelegates: context.localizationDelegates, + // theme settings + themeMode: appController.themeMode, + theme: appController.lightTheme, + darkTheme: appController.darkTheme, + // other preferences + debugShowCheckedModeBanner: false, + scrollBehavior: + const MaterialScrollBehavior().copyWith(scrollbars: false), + builder: _builder, + ), + ); + } + + Widget _builder(final BuildContext context, final Widget? widget) { + Widget error = const Center(child: Text('...rendering error...')); + if (widget is Scaffold || widget is Navigator) { + error = Scaffold(body: error); + } + ErrorWidget.builder = (final FlutterErrorDetails errorDetails) => error; + + return widget ?? error; + } +} diff --git a/lib/ui/pages/more/app_settings/app_settings.dart b/lib/ui/pages/more/app_settings/app_settings.dart index 7cce6715..b96fd749 100644 --- a/lib/ui/pages/more/app_settings/app_settings.dart +++ b/lib/ui/pages/more/app_settings/app_settings.dart @@ -1,10 +1,16 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; +import 'package:gap/gap.dart'; +import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/buttons/dialog_action_button.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/router/router.dart'; + +part 'language_picker.dart'; +part 'reset_app_button.dart'; +part 'theme_picker.dart'; @RoutePage() class AppSettingsPage extends StatefulWidget { @@ -16,82 +22,36 @@ class AppSettingsPage extends StatefulWidget { class _AppSettingsPageState extends State { @override - Widget build(final BuildContext context) { - final bool isDarkModeOn = - context.watch().state.isDarkModeOn; - - final bool isSystemDarkModeOn = - context.watch().state.isAutoDarkModeOn; - - return BrandHeroScreen( - hasBackButton: true, - hasFlashButton: false, - bodyPadding: const EdgeInsets.symmetric(vertical: 16), - heroTitle: 'application_settings.title'.tr(), - children: [ - SwitchListTile.adaptive( - title: Text('application_settings.system_dark_theme_title'.tr()), - subtitle: - Text('application_settings.system_dark_theme_description'.tr()), - value: isSystemDarkModeOn, - onChanged: (final value) => context - .read() - .updateAutoDarkMode(isAutoDarkModeOn: !isSystemDarkModeOn), + Widget build(final BuildContext context) => BrandHeroScreen( + hasBackButton: true, + hasFlashButton: false, + bodyPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 16, ), - SwitchListTile.adaptive( - title: Text('application_settings.dark_theme_title'.tr()), - subtitle: Text('application_settings.dark_theme_description'.tr()), - value: Theme.of(context).brightness == Brightness.dark, - onChanged: isSystemDarkModeOn - ? null - : (final value) => context - .read() - .updateDarkMode(isDarkModeOn: !isDarkModeOn), - ), - const Divider(height: 0), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'application_settings.dangerous_settings'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.error, - ), + heroTitle: 'application_settings.title'.tr(), + children: [ + _ThemePicker( + key: ValueKey('theme_picker'.tr()), ), - ), - const _ResetAppTile(), - ], - ); - } -} - -class _ResetAppTile extends StatelessWidget { - const _ResetAppTile(); - - @override - Widget build(final BuildContext context) => ListTile( - title: Text('application_settings.reset_config_title'.tr()), - subtitle: Text('application_settings.reset_config_description'.tr()), - onTap: () { - showDialog( - context: context, - builder: (final _) => AlertDialog( - title: Text('modals.are_you_sure'.tr()), - content: Text('modals.purge_all_keys'.tr()), - actions: [ - DialogActionButton( - text: 'modals.purge_all_keys_confirm'.tr(), - isRed: true, - onPressed: () { - context.read().clearAppConfig(); - Navigator.of(context).pop(); - }, - ), - DialogActionButton( - text: 'basis.cancel'.tr(), - ), - ], + const Divider(height: 5, thickness: 0), + _LanguagePicker( + key: ValueKey('language_picker'.tr()), + ), + const Divider(height: 5, thickness: 0), + const Gap(4), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + 'application_settings.dangerous_settings'.tr(), + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.error, + ), ), - ); - }, + ), + _ResetAppTile( + key: ValueKey('reset_app'.tr()), + ), + ], ); } diff --git a/lib/ui/pages/more/app_settings/developer_settings.dart b/lib/ui/pages/more/app_settings/developer_settings.dart index 751eabb6..51e2a2b3 100644 --- a/lib/ui/pages/more/app_settings/developer_settings.dart +++ b/lib/ui/pages/more/app_settings/developer_settings.dart @@ -1,11 +1,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; -import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/ui/components/list_tiles/section_title.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/router/router.dart'; @@ -60,17 +61,14 @@ class _DeveloperSettingsPageState extends State { title: Text('developer_settings.reset_onboarding'.tr()), subtitle: Text('developer_settings.reset_onboarding_description'.tr()), - enabled: - !context.watch().state.isOnboardingShowing, - onTap: () => context - .read() - .turnOffOnboarding(isOnboardingShowing: true), + enabled: !InheritedAppController.of(context).shouldShowOnboarding, + onTap: () => InheritedAppController.of(context) + .setShouldShowOnboarding(true), ), ListTile( title: Text('storage.start_migration_button'.tr()), subtitle: Text('storage.data_migration_notice'.tr()), - enabled: - !context.watch().state.isOnboardingShowing, + enabled: InheritedAppController.of(context).shouldShowOnboarding, onTap: () => context.pushRoute( ServicesMigrationRoute( diskStatus: context.read().state.diskStatus, diff --git a/lib/ui/pages/more/app_settings/language_picker.dart b/lib/ui/pages/more/app_settings/language_picker.dart new file mode 100644 index 00000000..75738ccc --- /dev/null +++ b/lib/ui/pages/more/app_settings/language_picker.dart @@ -0,0 +1,53 @@ +part of 'app_settings.dart'; + +class _LanguagePicker extends StatelessWidget { + const _LanguagePicker({super.key}); + + @override + Widget build(final BuildContext context) => ListTile( + title: Text( + 'application_settings.language'.tr(), + ), + subtitle: Text('application_settings.click_to_change_locale'.tr()), + trailing: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + context.locale.toString(), + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + onTap: () async { + final appController = InheritedAppController.of(context); + final Locale? newLocale = await showDialog( + context: context, + builder: (final context) => const _LanguagePickerDialog(), + routeSettings: _LanguagePickerDialog.routeSettings, + ); + + if (newLocale != null) { + await appController.setLocale(newLocale); + } + }, + ); +} + +class _LanguagePickerDialog extends StatelessWidget { + const _LanguagePickerDialog(); + static const routeSettings = RouteSettings(name: 'LanguagePickerDialog'); + + @override + Widget build(final BuildContext context) => SimpleDialog( + title: Text('application_settings.language'.tr()), + children: [ + for (final locale + in InheritedAppController.of(context).supportedLocales) + ListTile( + // TODO: add locale to language name matcher + title: Text(locale.toString()), + onTap: () { + Navigator.of(context).pop(locale); + }, + ) + ], + ); +} diff --git a/lib/ui/pages/more/app_settings/reset_app_button.dart b/lib/ui/pages/more/app_settings/reset_app_button.dart new file mode 100644 index 00000000..92ec3022 --- /dev/null +++ b/lib/ui/pages/more/app_settings/reset_app_button.dart @@ -0,0 +1,42 @@ +part of 'app_settings.dart'; + +class _ResetAppTile extends StatelessWidget { + const _ResetAppTile({super.key}); + + @override + Widget build(final BuildContext context) => ListTile( + title: Text('application_settings.reset_config_title'.tr()), + subtitle: Text('application_settings.reset_config_description'.tr()), + onTap: () => showDialog( + context: context, + builder: (final context) => const _ResetAppDialog(), + ), + ); +} + +class _ResetAppDialog extends StatelessWidget { + const _ResetAppDialog(); + + @override + Widget build(final BuildContext context) => AlertDialog( + title: Text('modals.are_you_sure'.tr()), + content: Text('modals.purge_all_keys'.tr()), + actions: [ + DialogActionButton( + text: 'modals.purge_all_keys_confirm'.tr(), + isRed: true, + onPressed: () { + context.read().clearAppConfig(); + + context.router.maybePop([ + const RootRoute(), + ]); + context.resetLocale(); + }, + ), + DialogActionButton( + text: 'basis.cancel'.tr(), + ), + ], + ); +} diff --git a/lib/ui/pages/more/app_settings/theme_picker.dart b/lib/ui/pages/more/app_settings/theme_picker.dart new file mode 100644 index 00000000..0e123045 --- /dev/null +++ b/lib/ui/pages/more/app_settings/theme_picker.dart @@ -0,0 +1,44 @@ +part of 'app_settings.dart'; + +class _ThemePicker extends StatelessWidget { + const _ThemePicker({super.key}); + + @override + Widget build(final BuildContext context) { + final appController = InheritedAppController.of(context); + // final themeMode = appController.themeMode; + // final bool isSystemThemeModeEnabled = themeMode == ThemeMode.system; + // final bool isDarkModeOn = themeMode == ThemeMode.dark; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SwitchListTile.adaptive( + title: Text('application_settings.system_theme_mode_title'.tr()), + subtitle: + Text('application_settings.system_theme_mode_description'.tr()), + value: appController.systemThemeModeActive, + onChanged: appController.setSystemThemeModeFlag, + // onChanged: (final newValue) => appController.setThemeMode( + // newValue + // ? ThemeMode.system + // : (isDarkModeOn ? ThemeMode.dark : ThemeMode.light), + // ), + ), + SwitchListTile.adaptive( + title: Text('application_settings.dark_theme_title'.tr()), + subtitle: Text('application_settings.change_application_theme'.tr()), + value: appController.darkThemeModeActive, + onChanged: appController.systemThemeModeActive + ? null + : appController.setDarkThemeModeFlag, + // onChanged: isSystemThemeModeEnabled + // ? null + // : (final newValue) => appController.setThemeMode( + // newValue ? ThemeMode.dark : ThemeMode.light, + // ), + ), + ], + ); + } +} diff --git a/lib/ui/pages/onboarding/onboarding.dart b/lib/ui/pages/onboarding/onboarding.dart index 141c9463..14d7a976 100644 --- a/lib/ui/pages/onboarding/onboarding.dart +++ b/lib/ui/pages/onboarding/onboarding.dart @@ -1,6 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; +import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart'; import 'package:selfprivacy/ui/pages/onboarding/views/views.dart'; import 'package:selfprivacy/ui/router/router.dart'; @@ -37,7 +37,8 @@ class _OnboardingPageState extends State { ), OnboardingSecondView( onProceed: () { - context.read().turnOffOnboarding(); + InheritedAppController.of(context) + .setShouldShowOnboarding(false); context.router.replaceAll([ const RootRoute(), const InitializingRoute(), diff --git a/lib/ui/pages/root_route.dart b/lib/ui/pages/root_route.dart index 6ae7607c..5bf0a45f 100644 --- a/lib/ui/pages/root_route.dart +++ b/lib/ui/pages/root_route.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; +import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/layouts/root_scaffold_with_navigation.dart'; import 'package:selfprivacy/ui/router/root_destinations.dart'; @@ -19,31 +19,33 @@ class RootPage extends StatefulWidget implements AutoRouteWrapper { } class _RootPageState extends State with TickerProviderStateMixin { - bool shouldUseSplitView() => false; + @override + void didChangeDependencies() { + if (InheritedAppController.of(context).shouldShowOnboarding) { + context.router.replace(const OnboardingRoute()); + } - final destinations = rootDestinations; + super.didChangeDependencies(); + } @override Widget build(final BuildContext context) { final bool isReady = context.watch().state is ServerInstallationFinished; - if (context.read().state.isOnboardingShowing) { - context.router.replace(const OnboardingRoute()); - } - return AutoRouter( builder: (final context, final child) { - final currentDestinationIndex = destinations.indexWhere( + final currentDestinationIndex = rootDestinations.indexWhere( (final destination) => context.router.isRouteActive(destination.route.routeName), ); final isOtherRouterActive = context.router.root.current.name != RootRoute.name; + final routeName = getRouteTitle(context.router.current.name).tr(); return RootScaffoldWithNavigation( title: routeName, - destinations: destinations, + destinations: rootDestinations, showBottomBar: !(currentDestinationIndex == -1 && !isOtherRouterActive), showFab: isReady, @@ -53,99 +55,3 @@ class _RootPageState extends State with TickerProviderStateMixin { ); } } - -class MainScreenNavigationRail extends StatelessWidget { - const MainScreenNavigationRail({ - required this.destinations, - super.key, - }); - - final List destinations; - - @override - Widget build(final BuildContext context) { - int? activeIndex = destinations.indexWhere( - (final destination) => - context.router.isRouteActive(destination.route.routeName), - ); - if (activeIndex == -1) { - activeIndex = null; - } - - return Padding( - padding: const EdgeInsets.all(8.0), - child: SizedBox( - height: MediaQuery.of(context).size.height, - width: 72, - child: LayoutBuilder( - builder: (final context, final constraints) => SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints(minHeight: constraints.maxHeight), - child: IntrinsicHeight( - child: NavigationRail( - backgroundColor: Colors.transparent, - labelType: NavigationRailLabelType.all, - destinations: destinations - .map( - (final destination) => NavigationRailDestination( - icon: Icon(destination.icon), - label: Text(destination.label), - ), - ) - .toList(), - selectedIndex: activeIndex, - onDestinationSelected: (final index) { - context.router.replaceAll([destinations[index].route]); - }, - ), - ), - ), - ), - ), - ), - ); - } -} - -class MainScreenNavigationDrawer extends StatelessWidget { - const MainScreenNavigationDrawer({ - required this.destinations, - super.key, - }); - - final List destinations; - - @override - Widget build(final BuildContext context) { - int? activeIndex = destinations.indexWhere( - (final destination) => - context.router.isRouteActive(destination.route.routeName), - ); - if (activeIndex == -1) { - activeIndex = null; - } - - return SizedBox( - height: MediaQuery.of(context).size.height, - width: 296, - child: LayoutBuilder( - builder: (final context, final constraints) => NavigationDrawer( - key: const Key('PrimaryNavigationDrawer'), - selectedIndex: activeIndex, - onDestinationSelected: (final index) { - context.router.replaceAll([destinations[index].route]); - }, - children: [ - const SizedBox(height: 18), - ...destinations.map( - (final destination) => NavigationDrawerDestination( - icon: Icon(destination.icon), - label: Text(destination.label), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/ui/pages/setup/initializing/server_type_picker.dart b/lib/ui/pages/setup/initializing/server_type_picker.dart index bdcabe92..25f559b2 100644 --- a/lib/ui/pages/setup/initializing/server_type_picker.dart +++ b/lib/ui/pages/setup/initializing/server_type_picker.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart'; import 'package:selfprivacy/illustrations/stray_deer.dart'; -import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart'; @@ -205,10 +205,8 @@ class SelectTypePage extends StatelessWidget { ), painter: StrayDeerPainter( colorScheme: Theme.of(context).colorScheme, - colorPalette: context - .read() - .state - .corePaletteOrDefault, + colorPalette: + InheritedAppController.of(context).corePalette, ), ), ), From 1e75dbcb81035ece934c2773b29eb71edcdc74df Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 19:45:04 +0400 Subject: [PATCH 22/37] feat: root_scaffold_with_subroutes rewrote root_scaffold_with_navigation: * extracted common code * removed dead one * cleaned up remaining one * fixed translations update on language change --- .../root_scaffold_with_navigation.dart | 295 ------------------ .../bottom_tab_bar.dart | 50 +++ .../navigation_drawer.dart | 35 +++ .../navigation_rail.dart | 47 +++ .../root_app_bar.dart | 50 +++ .../root_scaffold_with_subroute_selector.dart | 67 ++++ .../subroute_selector.dart | 33 ++ lib/ui/pages/root_route.dart | 7 +- 8 files changed, 284 insertions(+), 300 deletions(-) delete mode 100644 lib/ui/layouts/root_scaffold_with_navigation.dart create mode 100644 lib/ui/layouts/root_scaffold_with_subroute_selector/bottom_tab_bar.dart create mode 100644 lib/ui/layouts/root_scaffold_with_subroute_selector/navigation_drawer.dart create mode 100644 lib/ui/layouts/root_scaffold_with_subroute_selector/navigation_rail.dart create mode 100644 lib/ui/layouts/root_scaffold_with_subroute_selector/root_app_bar.dart create mode 100644 lib/ui/layouts/root_scaffold_with_subroute_selector/root_scaffold_with_subroute_selector.dart create mode 100644 lib/ui/layouts/root_scaffold_with_subroute_selector/subroute_selector.dart diff --git a/lib/ui/layouts/root_scaffold_with_navigation.dart b/lib/ui/layouts/root_scaffold_with_navigation.dart deleted file mode 100644 index d5332f24..00000000 --- a/lib/ui/layouts/root_scaffold_with_navigation.dart +++ /dev/null @@ -1,295 +0,0 @@ -import 'dart:io'; - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:selfprivacy/ui/components/drawers/support_drawer.dart'; -import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart'; -import 'package:selfprivacy/ui/router/root_destinations.dart'; -import 'package:selfprivacy/utils/breakpoints.dart'; - -class RootScaffoldWithNavigation extends StatelessWidget { - const RootScaffoldWithNavigation({ - required this.child, - required this.title, - required this.destinations, - this.showBottomBar = true, - this.showFab = true, - super.key, - }); - - final Widget child; - final String title; - final bool showBottomBar; - final List destinations; - final bool showFab; - - @override - // ignore: prefer_expression_function_bodies - Widget build(final BuildContext context) { - return Scaffold( - appBar: Breakpoints.mediumAndUp.isActive(context) - ? PreferredSize( - preferredSize: const Size.fromHeight(52), - child: _RootAppBar(title: title), - ) - : null, - endDrawer: const SupportDrawer(), - endDrawerEnableOpenDragGesture: false, - body: Row( - children: [ - if (Breakpoints.medium.isActive(context)) - _MainScreenNavigationRail( - destinations: destinations, - showFab: showFab, - ), - if (Breakpoints.large.isActive(context)) - _MainScreenNavigationDrawer( - destinations: destinations, - showFab: showFab, - ), - Expanded(child: child), - ], - ), - bottomNavigationBar: _BottomBar( - destinations: destinations, - hidden: !(Breakpoints.small.isActive(context) && showBottomBar), - key: const Key('bottomBar'), - ), - floatingActionButton: - showFab && Breakpoints.small.isActive(context) && showBottomBar - ? const BrandFab() - : null, - ); - } -} - -class _RootAppBar extends StatelessWidget { - const _RootAppBar({ - required this.title, - }); - - final String title; - - @override - Widget build(final BuildContext context) => AppBar( - title: AnimatedSwitcher( - duration: const Duration(milliseconds: 200), - transitionBuilder: - (final Widget child, final Animation animation) => - SlideTransition( - position: animation.drive( - Tween( - begin: const Offset(0.0, 0.2), - end: Offset.zero, - ), - ), - child: FadeTransition( - opacity: animation, - child: child, - ), - ), - child: SizedBox( - key: ValueKey(title), - width: double.infinity, - child: Text( - title, - ), - ), - ), - leading: context.router.pageCount > 1 - ? IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => context.router.maybePop(), - ) - : null, - actions: const [ - SizedBox.shrink(), - ], - ); -} - -class _MainScreenNavigationRail extends StatelessWidget { - const _MainScreenNavigationRail({ - required this.destinations, - this.showFab = true, - }); - - final List destinations; - final bool showFab; - - @override - Widget build(final BuildContext context) { - int? activeIndex = destinations.indexWhere( - (final destination) => - context.router.isRouteActive(destination.route.routeName), - ); - - final prevActiveIndex = destinations.indexWhere( - (final destination) => context.router.stack - .any((final route) => route.name == destination.route.routeName), - ); - - if (activeIndex == -1) { - if (prevActiveIndex != -1) { - activeIndex = prevActiveIndex; - } else { - activeIndex = 0; - } - } - - final isExtended = Breakpoints.large.isActive(context); - - return LayoutBuilder( - builder: (final context, final constraints) => SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints(minHeight: constraints.maxHeight), - child: IntrinsicHeight( - child: NavigationRail( - backgroundColor: Colors.transparent, - labelType: isExtended - ? NavigationRailLabelType.none - : NavigationRailLabelType.all, - extended: isExtended, - leading: showFab - ? const BrandFab( - extended: false, - ) - : null, - groupAlignment: 0.0, - destinations: destinations - .map( - (final destination) => NavigationRailDestination( - icon: Icon(destination.icon), - label: Text(destination.label), - ), - ) - .toList(), - selectedIndex: activeIndex, - onDestinationSelected: (final index) { - context.router.replaceAll([destinations[index].route]); - }, - ), - ), - ), - ), - ); - } -} - -class _BottomBar extends StatelessWidget { - const _BottomBar({ - required this.destinations, - required this.hidden, - super.key, - }); - - final List destinations; - final bool hidden; - - @override - Widget build(final BuildContext context) { - final prevActiveIndex = destinations.indexWhere( - (final destination) => context.router.stack - .any((final route) => route.name == destination.route.routeName), - ); - - return AnimatedContainer( - duration: const Duration(milliseconds: 500), - height: hidden ? 0 : 80, - curve: Curves.easeInOutCubicEmphasized, - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - ), - child: Platform.isIOS - ? CupertinoTabBar( - currentIndex: prevActiveIndex == -1 ? 0 : prevActiveIndex, - onTap: (final index) { - context.router.replaceAll([destinations[index].route]); - }, - items: destinations - .map( - (final destination) => BottomNavigationBarItem( - icon: Icon(destination.icon), - label: destination.label, - ), - ) - .toList(), - ) - : NavigationBar( - selectedIndex: prevActiveIndex == -1 ? 0 : prevActiveIndex, - labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, - onDestinationSelected: (final index) { - context.router.replaceAll([destinations[index].route]); - }, - destinations: destinations - .map( - (final destination) => NavigationDestination( - icon: Icon(destination.icon), - label: destination.label, - ), - ) - .toList(), - ), - ); - } -} - -class _MainScreenNavigationDrawer extends StatelessWidget { - const _MainScreenNavigationDrawer({ - required this.destinations, - this.showFab = true, - }); - - final List destinations; - final bool showFab; - - @override - Widget build(final BuildContext context) { - int? activeIndex = destinations.indexWhere( - (final destination) => - context.router.isRouteActive(destination.route.routeName), - ); - - final prevActiveIndex = destinations.indexWhere( - (final destination) => context.router.stack - .any((final route) => route.name == destination.route.routeName), - ); - - if (activeIndex == -1) { - if (prevActiveIndex != -1) { - activeIndex = prevActiveIndex; - } else { - activeIndex = 0; - } - } - - return SizedBox( - height: MediaQuery.of(context).size.height, - width: 296, - child: NavigationDrawer( - key: const Key('PrimaryNavigationDrawer'), - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - surfaceTintColor: Colors.transparent, - selectedIndex: activeIndex, - onDestinationSelected: (final index) { - context.router.replaceAll([destinations[index].route]); - }, - children: [ - const Padding( - padding: EdgeInsets.all(16.0), - child: BrandFab(extended: true), - ), - const SizedBox(height: 16), - ...destinations.map( - (final destination) => NavigationDrawerDestination( - icon: Icon(destination.icon), - label: Text(destination.label), - ), - ), - ], - ), - ); - } -} diff --git a/lib/ui/layouts/root_scaffold_with_subroute_selector/bottom_tab_bar.dart b/lib/ui/layouts/root_scaffold_with_subroute_selector/bottom_tab_bar.dart new file mode 100644 index 00000000..a6a5b43a --- /dev/null +++ b/lib/ui/layouts/root_scaffold_with_subroute_selector/bottom_tab_bar.dart @@ -0,0 +1,50 @@ +part of 'root_scaffold_with_subroute_selector.dart'; + +class _BottomTabBar extends SubrouteSelector { + const _BottomTabBar({ + required super.subroutes, + required this.hidden, + super.key, + }); + + final bool hidden; + + @override + Widget build(final BuildContext context) { + final int activeIndex = getActiveIndex(context); + + return AnimatedContainer( + duration: const Duration(milliseconds: 500), + height: hidden ? 0 : 80, + curve: Curves.easeInOutCubicEmphasized, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + ), + child: Platform.isIOS + ? CupertinoTabBar( + currentIndex: activeIndex, + onTap: openSubpage(context), + items: [ + for (final destination in subroutes) + BottomNavigationBarItem( + icon: Icon(destination.icon), + label: destination.label.tr(), + ), + ], + ) + : NavigationBar( + selectedIndex: activeIndex, + labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, + onDestinationSelected: openSubpage(context), + destinations: [ + for (final destination in subroutes) + NavigationDestination( + icon: Icon(destination.icon), + label: destination.label.tr(), + ), + ].toList(), + ), + ); + } +} diff --git a/lib/ui/layouts/root_scaffold_with_subroute_selector/navigation_drawer.dart b/lib/ui/layouts/root_scaffold_with_subroute_selector/navigation_drawer.dart new file mode 100644 index 00000000..31a9a47d --- /dev/null +++ b/lib/ui/layouts/root_scaffold_with_subroute_selector/navigation_drawer.dart @@ -0,0 +1,35 @@ +part of 'root_scaffold_with_subroute_selector.dart'; + +class _NavigationDrawer extends SubrouteSelector { + const _NavigationDrawer({ + required super.subroutes, + this.showFab = true, + }); + + final bool showFab; + + @override + Widget build(final BuildContext context) => SizedBox( + height: MediaQuery.of(context).size.height, + width: 296, + child: NavigationDrawer( + key: const Key('PrimaryNavigationDrawer'), + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + surfaceTintColor: Colors.transparent, + selectedIndex: getActiveIndex(context), + onDestinationSelected: openSubpage(context), + children: [ + const Padding( + padding: EdgeInsets.all(16.0), + child: BrandFab(extended: true), + ), + const SizedBox(height: 16), + for (final destination in subroutes) + NavigationDrawerDestination( + icon: Icon(destination.icon), + label: Text(destination.label.tr()), + ), + ], + ), + ); +} diff --git a/lib/ui/layouts/root_scaffold_with_subroute_selector/navigation_rail.dart b/lib/ui/layouts/root_scaffold_with_subroute_selector/navigation_rail.dart new file mode 100644 index 00000000..ef511018 --- /dev/null +++ b/lib/ui/layouts/root_scaffold_with_subroute_selector/navigation_rail.dart @@ -0,0 +1,47 @@ +part of 'root_scaffold_with_subroute_selector.dart'; + +class _NavigationRail extends SubrouteSelector { + const _NavigationRail({ + required super.subroutes, + this.showFab = true, + }); + + final bool showFab; + + @override + Widget build(final BuildContext context) { + final isExtended = Breakpoints.large.isActive(context); + + return LayoutBuilder( + builder: (final context, final constraints) => SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: NavigationRail( + backgroundColor: Colors.transparent, + labelType: isExtended + ? NavigationRailLabelType.none + : NavigationRailLabelType.all, + extended: isExtended, + leading: showFab + ? const BrandFab( + extended: false, + ) + : null, + groupAlignment: 0.0, + destinations: [ + for (final destination in subroutes) + NavigationRailDestination( + icon: Icon(destination.icon), + label: Text(destination.label.tr()), + ), + ], + selectedIndex: getActiveIndex(context), + onDestinationSelected: openSubpage(context), + ), + ), + ), + ), + ); + } +} diff --git a/lib/ui/layouts/root_scaffold_with_subroute_selector/root_app_bar.dart b/lib/ui/layouts/root_scaffold_with_subroute_selector/root_app_bar.dart new file mode 100644 index 00000000..b0d89bce --- /dev/null +++ b/lib/ui/layouts/root_scaffold_with_subroute_selector/root_app_bar.dart @@ -0,0 +1,50 @@ +part of 'root_scaffold_with_subroute_selector.dart'; + +class _RootAppBar extends StatelessWidget implements PreferredSizeWidget { + const _RootAppBar({ + required this.title, + }); + + final String title; + + @override + Size get preferredSize => const Size.fromHeight(52); + + @override + Widget build(final BuildContext context) => AppBar( + title: AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + transitionBuilder: + (final Widget child, final Animation animation) => + SlideTransition( + position: animation.drive( + Tween( + begin: const Offset(0.0, 0.2), + end: Offset.zero, + ), + ), + child: FadeTransition( + opacity: animation, + child: child, + ), + ), + child: SizedBox( + key: ValueKey(title), + width: double.infinity, + child: Text( + title, + maxLines: 1, + textAlign: TextAlign.start, + overflow: TextOverflow.fade, + ), + ), + ), + leading: context.router.pageCount > 1 + ? IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => context.router.maybePop(), + ) + : null, + actions: const [SizedBox.shrink()], + ); +} diff --git a/lib/ui/layouts/root_scaffold_with_subroute_selector/root_scaffold_with_subroute_selector.dart b/lib/ui/layouts/root_scaffold_with_subroute_selector/root_scaffold_with_subroute_selector.dart new file mode 100644 index 00000000..2c5ea6d2 --- /dev/null +++ b/lib/ui/layouts/root_scaffold_with_subroute_selector/root_scaffold_with_subroute_selector.dart @@ -0,0 +1,67 @@ +import 'dart:io'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/ui/components/drawers/support_drawer.dart'; +import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart'; +import 'package:selfprivacy/ui/router/root_destinations.dart'; +import 'package:selfprivacy/ui/router/router.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; + +part 'bottom_tab_bar.dart'; +part 'navigation_drawer.dart'; +part 'navigation_rail.dart'; +part 'root_app_bar.dart'; +part 'subroute_selector.dart'; + +class RootScaffoldWithSubrouteSelector extends StatelessWidget { + const RootScaffoldWithSubrouteSelector({ + required this.child, + required this.destinations, + this.showBottomBar = true, + this.showFab = true, + super.key, + }); + + final Widget child; + final bool showBottomBar; + final List destinations; + final bool showFab; + + @override + Widget build(final BuildContext context) => Scaffold( + appBar: Breakpoints.mediumAndUp.isActive(context) + ? _RootAppBar( + title: getRouteTitle(context.router.current.name).tr(), + ) + : null, + endDrawer: const SupportDrawer(), + endDrawerEnableOpenDragGesture: false, + body: Row( + children: [ + if (Breakpoints.medium.isActive(context)) + _NavigationRail( + subroutes: destinations, + showFab: showFab, + ) + else if (Breakpoints.large.isActive(context)) + _NavigationDrawer( + subroutes: destinations, + showFab: showFab, + ), + Expanded(child: child), + ], + ), + bottomNavigationBar: _BottomTabBar( + key: const ValueKey('bottomBar'), + subroutes: destinations, + hidden: !(Breakpoints.small.isActive(context) && showBottomBar), + ), + floatingActionButton: + showFab && Breakpoints.small.isActive(context) && showBottomBar + ? const BrandFab() + : null, + ); +} diff --git a/lib/ui/layouts/root_scaffold_with_subroute_selector/subroute_selector.dart b/lib/ui/layouts/root_scaffold_with_subroute_selector/subroute_selector.dart new file mode 100644 index 00000000..65e424d8 --- /dev/null +++ b/lib/ui/layouts/root_scaffold_with_subroute_selector/subroute_selector.dart @@ -0,0 +1,33 @@ +part of 'root_scaffold_with_subroute_selector.dart'; + +abstract class SubrouteSelector extends StatelessWidget { + const SubrouteSelector({ + required this.subroutes, + super.key, + }); + + final List subroutes; + + int getActiveIndex(final BuildContext context) { + int activeIndex = subroutes.indexWhere( + (final destination) => + context.router.isRouteActive(destination.route.routeName), + ); + + final prevActiveIndex = subroutes.indexWhere( + (final destination) => context.router.stack.any( + (final route) => route.name == destination.route.routeName, + ), + ); + + if (activeIndex == -1) { + activeIndex = prevActiveIndex != -1 ? prevActiveIndex : 0; + } + + return activeIndex; + } + + ValueSetter openSubpage(final BuildContext context) => (final index) { + context.router.replaceAll([subroutes[index].route]); + }; +} diff --git a/lib/ui/pages/root_route.dart b/lib/ui/pages/root_route.dart index 5bf0a45f..50da7178 100644 --- a/lib/ui/pages/root_route.dart +++ b/lib/ui/pages/root_route.dart @@ -1,9 +1,8 @@ import 'package:auto_route/auto_route.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/ui/layouts/root_scaffold_with_navigation.dart'; +import 'package:selfprivacy/ui/layouts/root_scaffold_with_subroute_selector/root_scaffold_with_subroute_selector.dart'; import 'package:selfprivacy/ui/router/root_destinations.dart'; import 'package:selfprivacy/ui/router/router.dart'; @@ -42,9 +41,7 @@ class _RootPageState extends State with TickerProviderStateMixin { final isOtherRouterActive = context.router.root.current.name != RootRoute.name; - final routeName = getRouteTitle(context.router.current.name).tr(); - return RootScaffoldWithNavigation( - title: routeName, + return RootScaffoldWithSubrouteSelector( destinations: rootDestinations, showBottomBar: !(currentDestinationIndex == -1 && !isOtherRouterActive), From fcf120bc0c59cb11f4b0e0fc06b934ec6ccfec9d Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 19:47:00 +0400 Subject: [PATCH 23/37] feat: list_tiles ink(button effects) now has circular(12) border. --- lib/theming/factory/app_theme_factory.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/theming/factory/app_theme_factory.dart b/lib/theming/factory/app_theme_factory.dart index e3ca53d9..096dd35a 100644 --- a/lib/theming/factory/app_theme_factory.dart +++ b/lib/theming/factory/app_theme_factory.dart @@ -42,6 +42,11 @@ abstract class AppThemeFactory { typography: appTypography, useMaterial3: true, scaffoldBackgroundColor: colorScheme.background, + listTileTheme: ListTileThemeData( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), ); return materialThemeData; From 5e27b369ca37c8d658f0e9688f8ba48e117d9524 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 19:47:41 +0400 Subject: [PATCH 24/37] chore: some missing async/awaits. --- lib/config/get_it_config.dart | 6 ++++-- .../server_installation/server_installation_repository.dart | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/config/get_it_config.dart b/lib/config/get_it_config.dart index aa3db90b..5f6d55cb 100644 --- a/lib/config/get_it_config.dart +++ b/lib/config/get_it_config.dart @@ -13,9 +13,11 @@ final GetIt getIt = GetIt.instance; Future getItSetup() async { getIt.registerSingleton(NavigationService()); - getIt.registerSingleton(ConsoleModel()); - getIt.registerSingleton(ApiConfigModel()..init()); + + final apiConfigModel = ApiConfigModel(); + await apiConfigModel.init(); + getIt.registerSingleton(apiConfigModel); getIt.registerSingleton( ApiConnectionRepository()..init(), diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 75daa0ab..b15cebae 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -501,7 +501,7 @@ class ServerInstallationRepository { Future deleteServerProviderKey() async { await box.delete(BNames.hetznerKey); - getIt().init(); + await getIt().init(); } Future saveBackblazeKey( @@ -512,7 +512,7 @@ class ServerInstallationRepository { Future deleteBackblazeKey() async { await box.delete(BNames.backblazeCredential); - getIt().init(); + await getIt().init(); } Future setDnsApiToken(final String key) async { @@ -521,7 +521,7 @@ class ServerInstallationRepository { Future deleteDnsProviderKey() async { await box.delete(BNames.cloudFlareKey); - getIt().init(); + await getIt().init(); } Future saveDomain(final ServerDomain serverDomain) async { From 70b2fc28abcb590467c5218b31cc47155937012e Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 19:49:50 +0400 Subject: [PATCH 25/37] chore: my personal generated files on macos. (inex asked for em) --- macos/Flutter/GeneratedPluginRegistrant.swift | 4 ++-- macos/Podfile.lock | 22 +++++++++---------- macos/Runner.xcodeproj/project.pbxproj | 8 +++++-- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 13f6db44..d831f93f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,7 +9,7 @@ import connectivity_plus import device_info_plus import dynamic_color import flutter_secure_storage_macos -import package_info +import package_info_plus import path_provider_foundation import shared_preferences_foundation import url_launcher_macos @@ -19,7 +19,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) - FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 7d720b06..1629a8ee 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -9,7 +9,7 @@ PODS: - flutter_secure_storage_macos (6.1.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - package_info (0.0.1): + - package_info_plus (0.0.1): - FlutterMacOS - path_provider_foundation (0.0.1): - Flutter @@ -27,7 +27,7 @@ DEPENDENCIES: - dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - package_info (from `Flutter/ephemeral/.symlinks/plugins/package_info/macos`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) @@ -47,8 +47,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos FlutterMacOS: :path: Flutter/ephemeral - package_info: - :path: Flutter/ephemeral/.symlinks/plugins/package_info/macos + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin shared_preferences_foundation: @@ -58,16 +58,16 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 - device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f + device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720 dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f - flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea + flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2 - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 - url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 PODFILE CHECKSUM: b0cc1fdf1eda0fefb5163971bbf18550427d02c4 -COCOAPODS: 1.15.2 +COCOAPODS: 1.15.1 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 37e2180a..f6ef3a25 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -202,7 +202,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -419,6 +419,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 46723VZHWZ; @@ -550,9 +551,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 46723VZHWZ; + DEVELOPMENT_TEAM = 7SWL2X7X4N; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = SelfPrivacy; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; @@ -562,6 +564,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 10.14; MARKETING_VERSION = 0.8.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.misterfourtytwo.selfprivacy; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -575,6 +578,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 46723VZHWZ; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index aa456674..ce01778a 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 15 May 2024 20:01:27 +0400 Subject: [PATCH 26/37] fix: brand_header now extends preferred_size_widget --- lib/ui/components/brand_header/brand_header.dart | 5 ++++- lib/ui/pages/more/more.dart | 7 ++----- lib/ui/pages/providers/providers.dart | 7 ++----- lib/ui/pages/services/services.dart | 7 ++----- lib/ui/pages/users/users.dart | 7 ++----- 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/lib/ui/components/brand_header/brand_header.dart b/lib/ui/components/brand_header/brand_header.dart index 56be04df..f2ba145f 100644 --- a/lib/ui/components/brand_header/brand_header.dart +++ b/lib/ui/components/brand_header/brand_header.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -class BrandHeader extends StatelessWidget { +class BrandHeader extends StatelessWidget implements PreferredSizeWidget { const BrandHeader({ super.key, this.title = '', @@ -8,6 +8,9 @@ class BrandHeader extends StatelessWidget { this.onBackButtonPressed, }); + @override + Size get preferredSize => const Size.fromHeight(52.0); + final String title; final bool hasBackButton; final VoidCallback? onBackButtonPressed; diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index 6e72bdd9..cf297328 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.dart @@ -21,11 +21,8 @@ class MorePage extends StatelessWidget { return Scaffold( appBar: Breakpoints.small.isActive(context) - ? PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'basis.more'.tr(), - ), + ? BrandHeader( + title: 'basis.more'.tr(), ) : null, body: ListView( diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index ec397805..8e70e609 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -65,11 +65,8 @@ class _ProvidersPageState extends State { return Scaffold( appBar: Breakpoints.small.isActive(context) - ? PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'basis.providers_title'.tr(), - ), + ? BrandHeader( + title: 'basis.providers_title'.tr(), ) : null, body: ListView( diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index 684af19a..15a1b3d9 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -37,11 +37,8 @@ class _ServicesPageState extends State { return Scaffold( appBar: Breakpoints.small.isActive(context) - ? PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'basis.services'.tr(), - ), + ? BrandHeader( + title: 'basis.services'.tr(), ) : null, body: !isReady diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index b7469a6e..ea282f95 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -129,11 +129,8 @@ class UsersPage extends StatelessWidget { return Scaffold( appBar: Breakpoints.small.isActive(context) - ? PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'basis.users'.tr(), - ), + ? BrandHeader( + title: 'basis.users'.tr(), ) : null, body: child, From c70edb957d03875483d7bd75efe9653c997c0b10 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 15 May 2024 20:20:59 +0400 Subject: [PATCH 27/37] fix: center empty_page_placeholder title alignment --- lib/ui/helpers/empty_page_placeholder.dart | 57 +++++++++++++++------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/lib/ui/helpers/empty_page_placeholder.dart b/lib/ui/helpers/empty_page_placeholder.dart index e96f91f9..5abc8434 100644 --- a/lib/ui/helpers/empty_page_placeholder.dart +++ b/lib/ui/helpers/empty_page_placeholder.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; class EmptyPagePlaceholder extends StatelessWidget { @@ -10,50 +11,72 @@ class EmptyPagePlaceholder extends StatelessWidget { super.key, }); + final bool showReadyCard; + final IconData iconData; final String title; final String description; - final IconData iconData; - final bool showReadyCard; @override - Widget build(final BuildContext context) => !showReadyCard - ? _expandedContent(context) - : Column( + Widget build(final BuildContext context) => showReadyCard + ? Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const Padding( - padding: EdgeInsets.symmetric(horizontal: 15), - child: NotReadyCard(), - ), - Expanded( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: Center( - child: _expandedContent(context), + if (showReadyCard) + const Padding( + padding: EdgeInsets.symmetric( + vertical: 15, + horizontal: 15, ), + child: NotReadyCard(), + ), + Expanded( + child: _ContentWidget( + iconData: iconData, + title: title, + description: description, ), ), ], + ) + : _ContentWidget( + iconData: iconData, + title: title, + description: description, ); +} - Widget _expandedContent(final BuildContext context) => Padding( +class _ContentWidget extends StatelessWidget { + const _ContentWidget({ + required this.iconData, + required this.title, + required this.description, + }); + + final IconData iconData; + final String title; + final String description; + + @override + Widget build(final BuildContext context) => Container( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon( iconData, size: 50, color: Theme.of(context).colorScheme.onBackground, ), - const SizedBox(height: 16), + const Gap(16), Text( title, style: Theme.of(context).textTheme.headlineMedium?.copyWith( color: Theme.of(context).colorScheme.onBackground, ), + textAlign: TextAlign.center, ), - const SizedBox(height: 8), + const Gap(8), Text( description, textAlign: TextAlign.center, From 0ee46e1c1ec2f3671bfb494c90a57f84a42e0935 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Sat, 18 May 2024 02:45:05 +0400 Subject: [PATCH 28/37] feat: native language names for locale picker --- assets/translations/de.json | 4 +- lib/config/localization.dart | 75 +++++++++++++------ .../pages/more/app_settings/app_settings.dart | 1 + .../more/app_settings/language_picker.dart | 14 +++- 4 files changed, 66 insertions(+), 28 deletions(-) diff --git a/assets/translations/de.json b/assets/translations/de.json index 354b8519..bffa69db 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -63,8 +63,8 @@ "change_application_theme": "Ihr Anwendungsdesign wechseln", "dangerous_settings": "Gefährliche Einstellungen", "reset_config_title": "Anwendungseinstellungen zurücksetzen", - "reset_config_description": "API Sclüssel und root Benutzer zurücksetzen.", - }, + "reset_config_description": "API Sclüssel und root Benutzer zurücksetzen." + }, "ssh": { "title": "SSH Schlüssel", "create": "SSH Schlüssel erstellen", diff --git a/lib/config/localization.dart b/lib/config/localization.dart index 64d734ee..697235b2 100644 --- a/lib/config/localization.dart +++ b/lib/config/localization.dart @@ -7,32 +7,63 @@ class Localization extends StatelessWidget { super.key, }); + // when adding new locale, add corresponding native language name to mapper + // below + static const supportedLocales = [ + Locale('ar'), + Locale('az'), + Locale('be'), + Locale('cs'), + Locale('de'), + Locale('en'), + Locale('es'), + Locale('et'), + Locale('fr'), + Locale('he'), + Locale('kk'), + Locale('lv'), + Locale('mk'), + Locale('pl'), + Locale('ru'), + Locale('sk'), + Locale('sl'), + Locale('th'), + Locale('uk'), + Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), + ]; + + // https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags + static final _languageNames = { + const Locale('ar'): 'العربية', + const Locale('az'): 'Azərbaycan', + const Locale('be'): 'беларуская', + const Locale('cs'): 'čeština', + const Locale('de'): 'Deutsch', + const Locale('en'): 'English', + const Locale('es'): 'español', + const Locale('et'): 'eesti', + const Locale('fr'): 'français', + const Locale('he'): 'עברית', + const Locale('kk'): 'Қазақша', + const Locale('lv'): 'latviešu', + const Locale('mk'): 'македонски јазик', + const Locale('pl'): 'polski', + const Locale('ru'): 'русский', + const Locale('sk'): 'slovenčina', + const Locale('sl'): 'slovenski', + const Locale('th'): 'ไทย', + const Locale('uk'): 'Українська', + const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'): '中文', + }; + + static String getLanguageName(final Locale locale) => + _languageNames[locale] ?? locale.languageCode; + final Widget child; @override Widget build(final BuildContext context) => EasyLocalization( - supportedLocales: const [ - Locale('ar'), - Locale('az'), - Locale('be'), - Locale('cs'), - Locale('de'), - Locale('en'), - Locale('es'), - Locale('et'), - Locale('fr'), - Locale('he'), - Locale('kk'), - Locale('lv'), - Locale('mk'), - Locale('pl'), - Locale('ru'), - Locale('sk'), - Locale('sl'), - Locale('th'), - Locale('uk'), - Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), - ], + supportedLocales: supportedLocales, path: 'assets/translations', fallbackLocale: const Locale('en'), useFallbackTranslations: true, diff --git a/lib/ui/pages/more/app_settings/app_settings.dart b/lib/ui/pages/more/app_settings/app_settings.dart index b96fd749..d429f2c0 100644 --- a/lib/ui/pages/more/app_settings/app_settings.dart +++ b/lib/ui/pages/more/app_settings/app_settings.dart @@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart'; +import 'package:selfprivacy/config/localization.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/buttons/dialog_action_button.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; diff --git a/lib/ui/pages/more/app_settings/language_picker.dart b/lib/ui/pages/more/app_settings/language_picker.dart index 75738ccc..e9b39747 100644 --- a/lib/ui/pages/more/app_settings/language_picker.dart +++ b/lib/ui/pages/more/app_settings/language_picker.dart @@ -12,7 +12,7 @@ class _LanguagePicker extends StatelessWidget { trailing: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text( - context.locale.toString(), + Localization.getLanguageName(context.locale), style: Theme.of(context).textTheme.headlineSmall, ), ), @@ -42,12 +42,18 @@ class _LanguagePickerDialog extends StatelessWidget { for (final locale in InheritedAppController.of(context).supportedLocales) ListTile( - // TODO: add locale to language name matcher - title: Text(locale.toString()), + title: Text( + Localization.getLanguageName(locale), + style: TextStyle( + fontWeight: locale == context.locale + ? FontWeight.w800 + : FontWeight.w400, + ), + ), onTap: () { Navigator.of(context).pop(locale); }, - ) + ), ], ); } From 4e0779f5e70aa48cb5115bac3c713872d1fd7322 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Mon, 20 May 2024 03:09:23 +0400 Subject: [PATCH 29/37] feat: some more work on console_page * console_log's copy data is now a valid json object for all log types * graphQLResponse now provides raw response object for copy * console_model now handles pause in itself, so UI pipeline doesn't disturb pause (like when revisiting page / hot reloading) * some minor console_page UI tweaks --- assets/translations/en.json | 10 +- .../graphql_maps/graphql_api_map.dart | 7 +- .../api_maps/rest_maps/rest_api_map.dart | 18 +- .../server_installation_repository.dart | 4 +- lib/logic/get_it/console_model.dart | 45 ++- lib/logic/models/console_log.dart | 88 ++++-- .../more/console/console_log_item_dialog.dart | 270 +++++++++++------- .../more/console/console_log_item_widget.dart | 60 ++-- lib/ui/pages/more/console/console_page.dart | 57 ++-- 9 files changed, 345 insertions(+), 214 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index d9cb98ab..ebbfb2a3 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -48,6 +48,7 @@ "title": "Console", "waiting": "Waiting for initialization…", "copy": "Copy", + "copy_raw": "Raw response", "historyEmpty": "No data yet", "error":"Error", "log":"Log", @@ -55,14 +56,19 @@ "rest_api_response":"Rest API Response", "graphql_request":"GraphQL Request", "graphql_response":"GraphQL Response", - "logged_at": "logged at: ", + "logged_at": "Logged at", "data": "Data", - "erros":"Errors", + "errors":"Errors", + "error_path": "Path", + "error_locations": "Locations", + "error_extensions": "Extensions", "request_data": "Request data", "headers": "Headers", "response_data": "Response data", "context": "Context", "operation": "Operation", + "operation_type": "Operation type", + "operation_name": "Operation name", "variables": "Variables" }, "about_application_page": { diff --git a/lib/logic/api_maps/graphql_maps/graphql_api_map.dart b/lib/logic/api_maps/graphql_maps/graphql_api_map.dart index 8723e188..6ae9fd20 100644 --- a/lib/logic/api_maps/graphql_maps/graphql_api_map.dart +++ b/lib/logic/api_maps/graphql_maps/graphql_api_map.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:graphql_flutter/graphql_flutter.dart'; @@ -17,9 +18,10 @@ class RequestLoggingLink extends Link { ]) async* { _addConsoleLog( GraphQlRequestConsoleLog( + // context: request.context, + operationType: request.type.name, operation: request.operation, variables: request.variables, - context: request.context, ), ); yield* forward!(request); @@ -32,9 +34,10 @@ class ResponseLoggingParser extends ResponseParser { final response = super.parseResponse(body); _addConsoleLog( GraphQlResponseConsoleLog( + // context: response.context, data: response.data, errors: response.errors, - context: response.context, + rawResponse: jsonEncode(response.response), ), ); return response; diff --git a/lib/logic/api_maps/rest_maps/rest_api_map.dart b/lib/logic/api_maps/rest_maps/rest_api_map.dart index 338b99c2..5426248f 100644 --- a/lib/logic/api_maps/rest_maps/rest_api_map.dart +++ b/lib/logic/api_maps/rest_maps/rest_api_map.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:developer'; import 'dart:io'; @@ -68,10 +69,10 @@ class ConsoleInterceptor extends InterceptorsWrapper { ) async { addConsoleLog( RestApiRequestConsoleLog( - method: options.method, - data: options.data.toString(), - headers: options.headers, uri: options.uri, + method: options.method, + headers: options.headers, + data: jsonEncode(options.data), ), ); return super.onRequest(options, handler); @@ -84,10 +85,10 @@ class ConsoleInterceptor extends InterceptorsWrapper { ) async { addConsoleLog( RestApiResponseConsoleLog( + uri: response.realUri, method: response.requestOptions.method, statusCode: response.statusCode, - data: response.data.toString(), - uri: response.realUri, + data: jsonEncode(response.data), ), ); return super.onResponse( @@ -103,12 +104,13 @@ class ConsoleInterceptor extends InterceptorsWrapper { ) async { final Response? response = err.response; log(err.toString()); + addConsoleLog( ManualConsoleLog.warning( customTitle: 'RestAPI error', - content: 'response-uri: ${response?.realUri}\n' - 'code: ${response?.statusCode}\n' - 'data: ${response?.toString()}\n', + content: '"uri": "${response?.realUri}",\n' + '"status_code": ${response?.statusCode},\n' + '"response": ${jsonEncode(response)}', ), ); return super.onError(err, handler); diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index b15cebae..de8a426d 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -530,7 +530,7 @@ class ServerInstallationRepository { Future deleteDomain() async { await box.delete(BNames.serverDomain); - getIt().init(); + await getIt().init(); } Future saveIsServerStarted(final bool value) async { @@ -604,6 +604,6 @@ class ServerInstallationRepository { BNames.hasFinalChecked, BNames.isLoading, ]); - getIt().init(); + await getIt().init(); } } diff --git a/lib/logic/get_it/console_model.dart b/lib/logic/get_it/console_model.dart index 9ef0d67e..6ec13264 100644 --- a/lib/logic/get_it/console_model.dart +++ b/lib/logic/get_it/console_model.dart @@ -2,15 +2,50 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/models/console_log.dart'; class ConsoleModel extends ChangeNotifier { + /// limit for history, so logs won't affect memory and overflow + static const logBufferLimit = 500; + + /// differs from log buffer limit so as to not rearrange memory each time + /// we add incoming log + static const incomingBufferBreakpoint = 750; + final List _logs = []; + final List _incomingQueue = []; + + bool _paused = false; + bool get paused => _paused; List get logs => _logs; void log(final ConsoleLog newLog) { - logs.add(newLog); - notifyListeners(); - // Make sure we don't have too many - if (logs.length > 500) { - logs.removeAt(0); + if (paused) { + _incomingQueue.add(newLog); + if (_incomingQueue.length > incomingBufferBreakpoint) { + logs.removeRange(0, _incomingQueue.length - logBufferLimit); + } + } else { + logs.add(newLog); + _updateQueue(); } } + + void play() { + _logs.addAll(_incomingQueue); + _paused = false; + _updateQueue(); + _incomingQueue.clear(); + } + + void pause() { + _paused = true; + notifyListeners(); + } + + /// drop logs over the limit and + void _updateQueue() { + // Make sure we don't have too many + if (logs.length > logBufferLimit) { + logs.removeRange(0, logs.length - logBufferLimit); + } + notifyListeners(); + } } diff --git a/lib/logic/models/console_log.dart b/lib/logic/models/console_log.dart index 6d982758..22e1c93e 100644 --- a/lib/logic/models/console_log.dart +++ b/lib/logic/models/console_log.dart @@ -1,5 +1,7 @@ -import 'package:gql/language.dart'; -import 'package:graphql/client.dart'; +import 'dart:convert'; + +import 'package:gql/language.dart' as gql; +import 'package:graphql/client.dart' as gql_client; import 'package:intl/intl.dart'; enum ConsoleLogSeverity { @@ -12,7 +14,6 @@ enum ConsoleLogSeverity { /// TODO(misterfourtytwo): should we add? /// /// * equality override -/// * translations of theese strings sealed class ConsoleLog { ConsoleLog({ final String? customTitle, @@ -32,13 +33,23 @@ sealed class ConsoleLog { String get content; /// data available for copy in dialog - String? get shareableData => '$title\n' - '{\n$content\n}'; + String? get shareableData => '{"title":"$title",\n' + '"timestamp": "$fullUTCString",\n' + '"data":{\n$content\n}' + '\n}'; static final DateFormat _formatter = DateFormat('hh:mm:ss'); String get timeString => _formatter.format(time); + + String get fullUTCString => time.toUtc().toIso8601String(); } +abstract class LogWithRawResponse { + String get rawResponse; +} + +/// entity for manually created logs, as opposed to automated ones coming +/// from requests / responses class ManualConsoleLog extends ConsoleLog { ManualConsoleLog({ required this.content, @@ -72,8 +83,10 @@ class RestApiRequestConsoleLog extends ConsoleLog { @override String get title => 'Rest API Request'; @override - String get content => 'method: $method\n' - 'uri: $uri'; + String get content => '"method": "$method",\n' + '"uri": "$uri",\n' + '"headers": ${jsonEncode(headers)},\n' + '"data": $data'; } class RestApiResponseConsoleLog extends ConsoleLog { @@ -93,49 +106,70 @@ class RestApiResponseConsoleLog extends ConsoleLog { @override String get title => 'Rest API Response'; @override - String get content => 'method: $method | status code: $statusCode\n' - 'uri: $uri'; + String get content => '"method": "$method",\n' + '"status_code": $statusCode,\n' + '"uri": "$uri",\n' + '"data": $data'; } +/// there is no actual getter for context fields outside of its class +/// one can extract unique entries by their type, which implements +/// `ContextEntry` class, I'll leave the code here if in the future +/// some entries will actually be needed. +// extension ContextEncoder on gql_client.Context { +// String get encode { +// return '""'; +// } +// } + class GraphQlRequestConsoleLog extends ConsoleLog { GraphQlRequestConsoleLog({ - this.operation, - this.variables, - this.context, + required this.operationType, + required this.operation, + required this.variables, + // this.context, super.severity, }); - final Context? context; - final Operation? operation; + // final gql_client.Context? context; + final String operationType; + final gql_client.Operation? operation; + String get operationDocument => + operation != null ? gql.printNode(operation!.document) : 'null'; final Map? variables; @override String get title => 'GraphQL Request'; @override - String get content => 'name: ${operation?.operationName}\n' - 'document: ${operation?.document != null ? printNode(operation!.document) : null}'; - String get stringifiedOperation => operation == null - ? 'null' - : 'Operation{\n' - '\tname: ${operation?.operationName},\n' - '\tdocument: ${operation?.document != null ? printNode(operation!.document) : null}\n' - '}'; + String get content => + // '"context": ${context?.encode},\n' + '"variables": ${jsonEncode(variables)},\n' + '"type": "$operationType",\n' + '"name": "${operation?.operationName}",\n' + '"document": ${jsonEncode(operationDocument)}'; } -class GraphQlResponseConsoleLog extends ConsoleLog { +class GraphQlResponseConsoleLog extends ConsoleLog + implements LogWithRawResponse { GraphQlResponseConsoleLog({ + required this.rawResponse, + // this.context, this.data, this.errors, - this.context, super.severity, }); - final Context? context; + @override + final String rawResponse; + // final gql_client.Context? context; final Map? data; - final List? errors; + final List? errors; @override String get title => 'GraphQL Response'; @override - String get content => 'data: $data'; + String get content => + // '"context": ${context?.encode},\n' + '"data": ${jsonEncode(data)},\n' + '"errors": $errors'; } diff --git a/lib/ui/pages/more/console/console_log_item_dialog.dart b/lib/ui/pages/more/console/console_log_item_dialog.dart index addd8ef9..15310103 100644 --- a/lib/ui/pages/more/console/console_log_item_dialog.dart +++ b/lib/ui/pages/more/console/console_log_item_dialog.dart @@ -7,66 +7,92 @@ extension on ConsoleLog { List unwrapContent(final BuildContext context) => switch (this) { (final RestApiRequestConsoleLog log) => [ if (log.method != null) _KeyValueRow('method', log.method), - if (log.uri != null) _KeyValueRow('uri', log.uri.toString()), + if (log.uri != null) _KeyValueRow('uri', '${log.uri}'), // headers bloc - if (log.headers?.isNotEmpty ?? false) + if (log.headers?.isNotEmpty ?? false) ...[ const _SectionRow('console_page.headers'), - ...?log.headers?.entries - .map((final entry) => _KeyValueRow(entry.key, entry.value)), + for (final entry in log.headers!.entries) + _KeyValueRow(entry.key, '${entry.value}'), + ], - // data bloc + // data const _SectionRow('console_page.data'), - _DataRow(log.data?.toString()), + _DataRow('${log.data}'), ], (final RestApiResponseConsoleLog log) => [ - if (log.method != null) _KeyValueRow('method', log.method), - if (log.uri != null) _KeyValueRow('uri', log.uri.toString()), + if (log.method != null) _KeyValueRow('method', '${log.method}'), + if (log.uri != null) _KeyValueRow('uri', '${log.uri}'), if (log.statusCode != null) - _KeyValueRow('statusCode', log.statusCode.toString()), + _KeyValueRow('statusCode', '${log.statusCode}'), - // data bloc + // data const _SectionRow('console_page.response_data'), - _DataRow(log.data?.toString()), + _DataRow('${log.data}'), ], (final GraphQlRequestConsoleLog log) => [ - // context - const _SectionRow('console_page.context'), - _DataRow(log.context?.toString()), - // data - if (log.operation != null) - const _SectionRow('console_page.operation'), - _DataRow(log.stringifiedOperation), // errors - if (log.variables?.isNotEmpty ?? false) - const _SectionRow('console_page.variables'), - ...?log.variables?.entries.map( - (final entry) => _KeyValueRow( - entry.key, - '${entry.value}', + // // context + // if (log.context != null) ...[ + // const _SectionRow('console_page.context'), + // _DataRow('${log.context}'), + // ], + + const _SectionRow('console_page.operation'), + if (log.operation != null) ...[ + _KeyValueRow( + 'console_page.operation_type'.tr(), + log.operationType, ), - ), + _KeyValueRow( + 'console_page.operation_name'.tr(), + log.operation?.operationName, + ), + const Divider(), + // data + _DataRow(log.operationDocument), + ], + // preset variables + if (log.variables?.isNotEmpty ?? false) ...[ + const _SectionRow('console_page.variables'), + for (final entry in log.variables!.entries) + _KeyValueRow(entry.key, '${entry.value}'), + ], ], (final GraphQlResponseConsoleLog log) => [ - // context - const _SectionRow('console_page.context'), - _DataRow(log.context?.toString()), + // // context + // const _SectionRow('console_page.context'), + // _DataRow('${log.context}'), // data - if (log.data != null) const _SectionRow('console_page.data'), - ...?log.data?.entries.map( - (final entry) => _KeyValueRow( - entry.key, - '${entry.value}', - ), - ), + if (log.data != null) ...[ + const _SectionRow('console_page.data'), + for (final entry in log.data!.entries) + _KeyValueRow(entry.key, '${entry.value}'), + ], // errors - if (log.errors?.isNotEmpty ?? false) + if (log.errors?.isNotEmpty ?? false) ...[ const _SectionRow('console_page.errors'), - ...?log.errors?.map( - (final entry) => _KeyValueRow( - entry.message, - '${entry.locations}', - ), - ), + for (final entry in log.errors!) ...[ + _KeyValueRow( + '${'console_page.error_message'.tr()}: ', + entry.message, + ), + _KeyValueRow( + '${'console_page.error_path'.tr()}: ', + '${entry.path}', + ), + if (entry.locations?.isNotEmpty ?? false) + _KeyValueRow( + '${'console_page.error_locations'.tr()}: ', + '${entry.locations}', + ), + if (entry.extensions?.isNotEmpty ?? false) + _KeyValueRow( + '${'console_page.error_extensions'.tr()}: ', + '${entry.extensions}', + ), + const Divider(), + ], + ], ], (final ManualConsoleLog log) => [ _DataRow(log.content), @@ -74,6 +100,7 @@ extension on ConsoleLog { }; } +/// dialog with detailed log content class ConsoleItemDialog extends StatelessWidget { const ConsoleItemDialog({ required this.log, @@ -83,80 +110,66 @@ class ConsoleItemDialog extends StatelessWidget { final ConsoleLog log; @override - Widget build(final BuildContext context) { - final content = log.unwrapContent(context); - - return AlertDialog( - scrollable: true, - title: Text(log.title), - content: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - Text('logged_at'.tr()), - SelectableText( - log.timeString, - style: const TextStyle( - fontWeight: FontWeight.w700, - fontFeatures: [FontFeature.tabularFigures()], + Widget build(final BuildContext context) => AlertDialog( + scrollable: true, + title: Text(log.title), + contentPadding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 12, + ), + content: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Divider(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: SelectableText.rich( + TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: '${'console_page.logged_at'.tr()}: ', + style: const TextStyle(), + ), + TextSpan( + text: '${log.timeString} (${log.fullUTCString})', + style: const TextStyle( + fontWeight: FontWeight.w700, + fontFeatures: [FontFeature.tabularFigures()], + ), + ), + ], ), ), - ], - ), - const Divider(), - ...content, - ], - ), - actions: [ - // A button to copy the request to the clipboard - if (log.shareableData?.isNotEmpty ?? false) - TextButton( - onPressed: () => PlatformAdapter.setClipboard(log.shareableData!), - child: Text('console_page.copy'.tr()), - ), - // close dialog - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('basis.close'.tr()), - ), - ], - ); - } -} - -class _KeyValueRow extends StatelessWidget { - const _KeyValueRow(this.title, this.value); - - final String title; - final String? value; - - @override - Widget build(final BuildContext context) => SelectableText.rich( - TextSpan( - style: DefaultTextStyle.of(context).style, - children: [ - TextSpan( - text: '$title: ', - style: const TextStyle(fontWeight: FontWeight.bold), ), - TextSpan(text: value ?? ''), + const Divider(), + ...log.unwrapContent(context), ], ), + actions: [ + if (log is LogWithRawResponse) + TextButton( + onPressed: () => PlatformAdapter.setClipboard( + (log as LogWithRawResponse).rawResponse, + ), + child: Text('console_page.copy_raw'.tr()), + ), + // A button to copy the request to the clipboard + if (log.shareableData?.isNotEmpty ?? false) + TextButton( + onPressed: () => PlatformAdapter.setClipboard(log.shareableData!), + child: Text('console_page.copy'.tr()), + ), + // close dialog + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('basis.close'.tr()), + ), + ], ); } -class _DataRow extends StatelessWidget { - const _DataRow(this.data); - final String? data; - - @override - Widget build(final BuildContext context) => SelectableText( - data ?? 'null', - style: const TextStyle(fontWeight: FontWeight.w400), - ); -} - +/// different sections delimiter with `title` class _SectionRow extends StatelessWidget { const _SectionRow(this.title); @@ -184,3 +197,44 @@ class _SectionRow extends StatelessWidget { ), ); } + +/// data row with a {key: value} pair +class _KeyValueRow extends StatelessWidget { + const _KeyValueRow(this.title, this.value); + + final String title; + final String? value; + + @override + Widget build(final BuildContext context) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: SelectableText.rich( + TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: '$title: ', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: value ?? ''), + ], + ), + ), + ); +} + +/// data row with only text +class _DataRow extends StatelessWidget { + const _DataRow(this.data); + + final String? data; + + @override + Widget build(final BuildContext context) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: SelectableText( + data ?? 'null', + style: const TextStyle(fontWeight: FontWeight.w400), + ), + ); +} diff --git a/lib/ui/pages/more/console/console_log_item_widget.dart b/lib/ui/pages/more/console/console_log_item_widget.dart index f64d1a50..71d4c138 100644 --- a/lib/ui/pages/more/console/console_log_item_widget.dart +++ b/lib/ui/pages/more/console/console_log_item_widget.dart @@ -35,37 +35,41 @@ class ConsoleLogItemWidget extends StatelessWidget { final ConsoleLog log; @override - Widget build(final BuildContext context) => ListTile( - dense: true, - title: SelectableText.rich( - TextSpan( - style: DefaultTextStyle.of(context).style, - children: [ - TextSpan( - text: '${log.timeString}: ', - style: const TextStyle( - fontFeatures: [FontFeature.tabularFigures()], + Widget build(final BuildContext context) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: ListTile( + dense: true, + title: Text.rich( + TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: '${log.timeString}: ', + style: const TextStyle( + fontFeatures: [FontFeature.tabularFigures()], + ), ), - ), - TextSpan( - text: log.title, - style: const TextStyle( - fontWeight: FontWeight.bold, + TextSpan( + text: log.title, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), - ), - ], + ], + ), + ), + subtitle: Text( + log.content, + overflow: TextOverflow.ellipsis, + maxLines: 3, + ), + leading: Icon(log.resolveIcon()), + iconColor: log.resolveColor(context), + onTap: () => showDialog( + context: context, + builder: (final BuildContext context) => + ConsoleItemDialog(log: log), ), - ), - subtitle: Text( - log.content, - overflow: TextOverflow.ellipsis, - maxLines: 3, - ), - leading: Icon(log.resolveIcon()), - iconColor: log.resolveColor(context), - onTap: () => showDialog( - context: context, - builder: (final BuildContext context) => ConsoleItemDialog(log: log), ), ); } diff --git a/lib/ui/pages/more/console/console_page.dart b/lib/ui/pages/more/console/console_page.dart index 83618ba2..95b77d73 100644 --- a/lib/ui/pages/more/console/console_page.dart +++ b/lib/ui/pages/more/console/console_page.dart @@ -16,8 +16,9 @@ class ConsolePage extends StatefulWidget { } class _ConsolePageState extends State { + ConsoleModel get console => getIt(); + /// should freeze logs state to properly read logs - bool paused = false; late final Future future; @override @@ -25,12 +26,12 @@ class _ConsolePageState extends State { super.initState(); future = getIt.allReady(); - getIt().addListener(update); + console.addListener(update); } @override void dispose() { - getIt().removeListener(update); + console.removeListener(update); super.dispose(); } @@ -40,17 +41,12 @@ class _ConsolePageState extends State { /// unmounted or during frame build, adding as postframe callback ensures /// that element is marked for rebuild WidgetsBinding.instance.addPostFrameCallback((final _) { - if (!paused && mounted) { + if (mounted) { setState(() => {}); } }); } - void togglePause() { - paused ^= true; - setState(() {}); - } - @override Widget build(final BuildContext context) => SafeArea( child: Scaffold( @@ -63,34 +59,31 @@ class _ConsolePageState extends State { actions: [ IconButton( icon: Icon( - paused ? Icons.play_arrow_outlined : Icons.pause_outlined, + console.paused + ? Icons.play_arrow_outlined + : Icons.pause_outlined, ), - onPressed: togglePause, + onPressed: console.paused ? console.play : console.pause, ), ], ), - body: SelectionArea( - child: Scrollbar( - child: FutureBuilder( - future: future, - builder: ( - final BuildContext context, - final AsyncSnapshot snapshot, - ) { - if (snapshot.hasData) { - final List logs = - getIt.get().logs; + body: Scrollbar( + child: FutureBuilder( + future: future, + builder: ( + final BuildContext context, + final AsyncSnapshot snapshot, + ) { + if (snapshot.hasData) { + final List logs = console.logs; - return logs.isEmpty - ? const _ConsoleViewEmpty() - : _ConsoleViewLoaded( - logs: logs, - ); - } + return logs.isEmpty + ? const _ConsoleViewEmpty() + : _ConsoleViewLoaded(logs: logs); + } - return const _ConsoleViewLoading(); - }, - ), + return const _ConsoleViewLoading(); + }, ), ), ), @@ -135,7 +128,7 @@ class _ConsoleViewLoaded extends StatelessWidget { @override Widget build(final BuildContext context) => ListView.separated( primary: true, - padding: const EdgeInsets.symmetric(vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 8), itemCount: logs.length, itemBuilder: (final BuildContext context, final int index) { final log = logs[logs.length - 1 - index]; From efd3dfbde5dca7874a43266d60aed30c4693b2b5 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Thu, 13 Jun 2024 21:53:06 +0400 Subject: [PATCH 30/37] feat: obscure/remove auth headers from console logs --- lib/logic/models/console_log.dart | 16 ++-- .../more/console/console_log_item_dialog.dart | 79 ++++++++++++++++--- pubspec.lock | 8 ++ pubspec.yaml | 1 + 4 files changed, 89 insertions(+), 15 deletions(-) diff --git a/lib/logic/models/console_log.dart b/lib/logic/models/console_log.dart index 22e1c93e..91cef018 100644 --- a/lib/logic/models/console_log.dart +++ b/lib/logic/models/console_log.dart @@ -10,10 +10,6 @@ enum ConsoleLogSeverity { } /// Base entity for console logs. -/// -/// TODO(misterfourtytwo): should we add? -/// -/// * equality override sealed class ConsoleLog { ConsoleLog({ final String? customTitle, @@ -75,6 +71,8 @@ class RestApiRequestConsoleLog extends ConsoleLog { super.severity, }); + static const blacklistedHeaders = ['Authorization']; + final String? method; final Uri? uri; final Map? headers; @@ -82,10 +80,18 @@ class RestApiRequestConsoleLog extends ConsoleLog { @override String get title => 'Rest API Request'; + + Map get filteredHeaders => Map.fromEntries( + headers?.entries.where( + (final entry) => !blacklistedHeaders.contains(entry.key), + ) ?? + const [], + ); + @override String get content => '"method": "$method",\n' '"uri": "$uri",\n' - '"headers": ${jsonEncode(headers)},\n' + '"headers": ${jsonEncode(filteredHeaders)},\n' // censor header to not expose API keys '"data": $data'; } diff --git a/lib/ui/pages/more/console/console_log_item_dialog.dart b/lib/ui/pages/more/console/console_log_item_dialog.dart index 15310103..5cb20142 100644 --- a/lib/ui/pages/more/console/console_log_item_dialog.dart +++ b/lib/ui/pages/more/console/console_log_item_dialog.dart @@ -1,4 +1,5 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/models/console_log.dart'; import 'package:selfprivacy/utils/platform_adapter.dart'; @@ -202,23 +203,81 @@ class _SectionRow extends StatelessWidget { class _KeyValueRow extends StatelessWidget { const _KeyValueRow(this.title, this.value); + static const List hideList = ['Authorization']; + final String title; final String? value; + @override + Widget build(final BuildContext context) => hideList.contains(title) + ? _ObscuredKeyValueRow(title, value) + : Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: SelectableText.rich( + TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: '$title: ', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: value ?? ''), + ], + ), + ), + ); +} + +class _ObscuredKeyValueRow extends StatefulWidget { + const _ObscuredKeyValueRow(this.title, this.value); + + final String title; + final String? value; + + @override + State<_ObscuredKeyValueRow> createState() => _ObscuredKeyValueRowState(); +} + +class _ObscuredKeyValueRowState extends State<_ObscuredKeyValueRow> { + static const obscuringCharacter = '•'; + bool _obscureValue = true; + @override Widget build(final BuildContext context) => Padding( padding: const EdgeInsets.symmetric(horizontal: 12), - child: SelectableText.rich( - TextSpan( - style: DefaultTextStyle.of(context).style, - children: [ - TextSpan( - text: '$title: ', - style: const TextStyle(fontWeight: FontWeight.bold), + child: Row( + children: [ + Expanded( + child: SelectableText.rich( + TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: '${widget.title}: ', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: _obscureValue + ? obscuringCharacter * (widget.value?.length ?? 4) + : widget.value ?? '', + style: const TextStyle( + fontFeatures: [FontFeature.tabularFigures()], + ), + ), + ], + ), ), - TextSpan(text: value ?? ''), - ], - ), + ), + IconButton( + icon: Icon( + _obscureValue ? CupertinoIcons.eye : CupertinoIcons.eye_slash, + ), + onPressed: () { + _obscureValue ^= true; // toggle value + setState(() {}); + }, + ), + ], ), ); } diff --git a/pubspec.lock b/pubspec.lock index 7187e937..8f635adb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -257,6 +257,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + cupertino_icons: + dependency: "direct dev" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" dart_style: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6c7ad7d3..3872d6e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,7 @@ dependencies: dev_dependencies: auto_route_generator: ^8.0.0 build_runner: ^2.4.9 + cupertino_icons: ^1.0.8 flutter_launcher_icons: ^0.13.1 flutter_lints: ^3.0.2 flutter_test: From 38a896ec2ea47af4c781b2f3c66b29454b3b9561 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Thu, 13 Jun 2024 22:11:08 +0400 Subject: [PATCH 31/37] fix: app_settings page UI updates --- lib/ui/pages/more/app_settings/app_settings.dart | 3 ++- lib/ui/pages/more/app_settings/language_picker.dart | 12 +++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/ui/pages/more/app_settings/app_settings.dart b/lib/ui/pages/more/app_settings/app_settings.dart index d429f2c0..ae67c825 100644 --- a/lib/ui/pages/more/app_settings/app_settings.dart +++ b/lib/ui/pages/more/app_settings/app_settings.dart @@ -45,11 +45,12 @@ class _AppSettingsPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( 'application_settings.dangerous_settings'.tr(), - style: Theme.of(context).textTheme.titleLarge!.copyWith( + style: Theme.of(context).textTheme.labelLarge!.copyWith( color: Theme.of(context).colorScheme.error, ), ), ), + const Gap(4), _ResetAppTile( key: ValueKey('reset_app'.tr()), ), diff --git a/lib/ui/pages/more/app_settings/language_picker.dart b/lib/ui/pages/more/app_settings/language_picker.dart index e9b39747..d4cb9607 100644 --- a/lib/ui/pages/more/app_settings/language_picker.dart +++ b/lib/ui/pages/more/app_settings/language_picker.dart @@ -13,7 +13,7 @@ class _LanguagePicker extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text( Localization.getLanguageName(context.locale), - style: Theme.of(context).textTheme.headlineSmall, + style: Theme.of(context).textTheme.labelLarge, ), ), onTap: () async { @@ -41,8 +41,10 @@ class _LanguagePickerDialog extends StatelessWidget { children: [ for (final locale in InheritedAppController.of(context).supportedLocales) - ListTile( - title: Text( + RadioMenuButton( + groupValue: context.locale, + value: locale, + child: Text( Localization.getLanguageName(locale), style: TextStyle( fontWeight: locale == context.locale @@ -50,8 +52,8 @@ class _LanguagePickerDialog extends StatelessWidget { : FontWeight.w400, ), ), - onTap: () { - Navigator.of(context).pop(locale); + onChanged: (final newValue) { + Navigator.of(context).pop(newValue); }, ), ], From bd090b646dc6ddbd27d2577886d7aeda75627242 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Sun, 16 Jun 2024 04:12:59 +0400 Subject: [PATCH 32/37] feat: reset locale to system default from language settings, removed dead code theme_picker code --- lib/config/app_controller/app_controller.dart | 56 ++++++++----------- lib/config/localization.dart | 4 ++ .../datasources/preferences_datasource.dart | 5 +- .../preferences_hive_datasource.dart | 5 +- .../inherited_preferences_repository.dart | 1 + .../preferences_repository.dart | 16 +++--- .../graphql_maps/graphql_api_map.dart | 2 +- lib/logic/get_it/api_config.dart | 11 ++-- .../more/app_settings/language_picker.dart | 41 +++++++------- .../pages/more/app_settings/theme_picker.dart | 13 ----- 10 files changed, 68 insertions(+), 86 deletions(-) diff --git a/lib/config/app_controller/app_controller.dart b/lib/config/app_controller/app_controller.dart index a14eaa19..f78bae72 100644 --- a/lib/config/app_controller/app_controller.dart +++ b/lib/config/app_controller/app_controller.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:material_color_utilities/material_color_utilities.dart' as color_utils; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/config/localization.dart'; import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart'; /// A class that many Widgets can interact with to read current app @@ -45,18 +46,10 @@ class AppController with ChangeNotifier { : darkThemeModeActive ? ThemeMode.dark : ThemeMode.light; - // // Make ThemeMode a private variable so it is not updated directly without - // // also persisting the changes with the repo.. - // late ThemeMode _themeMode; - // // Allow Widgets to read the user's preferred ThemeMode. - // ThemeMode get themeMode => _themeMode; late bool _shouldShowOnboarding; bool get shouldShowOnboarding => _shouldShowOnboarding; - /// Load the user's settings from the SettingsService. It may load from a - /// local database or the internet. The controller only knows it can load the - /// settings from the service. Future init({ // required final AppPreferencesRepository repo, required final ThemeData lightThemeData, @@ -68,12 +61,17 @@ class AppController with ChangeNotifier { await Future.wait([ // load locale () async { - _supportedLocales = await _repo.getSupportedLocales(); + _supportedLocales = [ + Localization.systemLocale, + ...await _repo.getSupportedLocales(), + ]; _locale = await _repo.getActiveLocale(); - // preset value to other state holders - await _apiConfigModel.setLocaleCode(_locale.languageCode); - await _repo.setDelegateLocale(_locale); + if (_locale != Localization.systemLocale) { + // preset value to other state holders + await _apiConfigModel.setLocaleCode(_locale.languageCode); + await _repo.setDelegateLocale(_locale); + } }(), // load theme mode && initialize theme @@ -81,7 +79,6 @@ class AppController with ChangeNotifier { _lightTheme = lightThemeData; _darkTheme = darkThemeData; _corePalette = colorPalette; - // _themeMode = await _repo.getThemeMode(); _darkThemeModeActive = await _repo.getDarkThemeModeFlag(); _systemThemeModeActive = await _repo.getSystemThemeModeFlag(); }(), @@ -98,7 +95,6 @@ class AppController with ChangeNotifier { } // updateRepoReference - Future setShouldShowOnboarding(final bool newValue) async { // Do not perform any work if new and old flag values are identical if (newValue == shouldShowOnboarding) { @@ -107,7 +103,6 @@ class AppController with ChangeNotifier { // Store the flag in memory _shouldShowOnboarding = newValue; - notifyListeners(); // Persist the change @@ -146,23 +141,6 @@ class AppController with ChangeNotifier { await _repo.setDarkThemeModeFlag(newValue); } - // /// Update and persist the ThemeMode based on the user's selection. - // Future setThemeMode(final ThemeMode newThemeMode) async { - // // Do not perform any work if new and old ThemeMode are identical - // if (newThemeMode == themeMode) { - // return; - // } - - // // Store the new ThemeMode in memory - // _themeMode = newThemeMode; - - // // Inform listeners a change has occurred. - // notifyListeners(); - - // // Persist the change - // await _repo.setThemeMode(newThemeMode); - // } - Future setLocale(final Locale newLocale) async { // Do not perform any work if new and old Locales are identical if (newLocale == _locale) { @@ -172,6 +150,10 @@ class AppController with ChangeNotifier { // Store the new Locale in memory _locale = newLocale; + if (newLocale == Localization.systemLocale) { + return resetLocale(); + } + /// update locale delegate, which in turn should update deps await _repo.setDelegateLocale(newLocale); @@ -180,4 +162,14 @@ class AppController with ChangeNotifier { // Update other locale holders await _apiConfigModel.setLocaleCode(newLocale.languageCode); } + + Future resetLocale() async { + /// update locale delegate, which in turn should update deps + await _repo.resetDelegateLocale(); + + // Persist the change + await _repo.resetActiveLocale(); + // Update other locale holders + await _apiConfigModel.resetLocaleCode(); + } } diff --git a/lib/config/localization.dart b/lib/config/localization.dart index 697235b2..e5da63ad 100644 --- a/lib/config/localization.dart +++ b/lib/config/localization.dart @@ -7,6 +7,9 @@ class Localization extends StatelessWidget { super.key, }); + /// value for resetting locale in settings to system default + static const systemLocale = Locale('system'); + // when adding new locale, add corresponding native language name to mapper // below static const supportedLocales = [ @@ -34,6 +37,7 @@ class Localization extends StatelessWidget { // https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags static final _languageNames = { + systemLocale: 'System default', const Locale('ar'): 'العربية', const Locale('az'): 'Azərbaycan', const Locale('be'): 'беларуская', diff --git a/lib/config/preferences_repository/datasources/preferences_datasource.dart b/lib/config/preferences_repository/datasources/preferences_datasource.dart index 53ef8b09..8c8498dd 100644 --- a/lib/config/preferences_repository/datasources/preferences_datasource.dart +++ b/lib/config/preferences_repository/datasources/preferences_datasource.dart @@ -29,8 +29,5 @@ abstract class PreferencesDataSource { Future getLocale(); /// locale, as set by user - /// - /// - /// when null, app takes system locale - Future setLocale(final String newLocale); + Future setLocale(final String? newLocale); } diff --git a/lib/config/preferences_repository/datasources/preferences_hive_datasource.dart b/lib/config/preferences_repository/datasources/preferences_hive_datasource.dart index 80dd9f11..f4e30130 100644 --- a/lib/config/preferences_repository/datasources/preferences_hive_datasource.dart +++ b/lib/config/preferences_repository/datasources/preferences_hive_datasource.dart @@ -34,6 +34,7 @@ class PreferencesHiveDataSource implements PreferencesDataSource { Future getLocale() async => _appSettingsBox.get(BNames.appLocale); @override - Future setLocale(final String newLocale) async => - _appSettingsBox.put(BNames.appLocale, newLocale); + Future setLocale(final String? newLocale) async => newLocale == null + ? _appSettingsBox.delete(BNames.appLocale) + : _appSettingsBox.put(BNames.appLocale, newLocale); } diff --git a/lib/config/preferences_repository/inherited_preferences_repository.dart b/lib/config/preferences_repository/inherited_preferences_repository.dart index 4a2881b1..c90dc91b 100644 --- a/lib/config/preferences_repository/inherited_preferences_repository.dart +++ b/lib/config/preferences_repository/inherited_preferences_repository.dart @@ -50,6 +50,7 @@ class _InheritedPreferencesRepositoryState repo = PreferencesRepository( dataSource: widget.dataSource, setDelegateLocale: EasyLocalization.of(context)!.setLocale, + resetDelegateLocale: EasyLocalization.of(context)!.resetLocale, getDelegateLocale: () => EasyLocalization.of(context)!.locale, getSupportedLocales: () => EasyLocalization.of(context)!.supportedLocales, ); diff --git a/lib/config/preferences_repository/preferences_repository.dart b/lib/config/preferences_repository/preferences_repository.dart index b649d38a..086156e5 100644 --- a/lib/config/preferences_repository/preferences_repository.dart +++ b/lib/config/preferences_repository/preferences_repository.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/localization.dart'; import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart'; class PreferencesRepository { @@ -9,6 +10,7 @@ class PreferencesRepository { required this.getSupportedLocales, required this.getDelegateLocale, required this.setDelegateLocale, + required this.resetDelegateLocale, }); final PreferencesDataSource dataSource; @@ -18,6 +20,7 @@ class PreferencesRepository { /// containing needed functions, but perceive it as boilerplate, because we /// don't need additional encapsulation level here) final FutureOr Function(Locale) setDelegateLocale; + final FutureOr Function() resetDelegateLocale; final FutureOr> Function() getSupportedLocales; final FutureOr Function() getDelegateLocale; @@ -36,13 +39,6 @@ class PreferencesRepository { Future setSystemModeFlag(final bool newValue) async => dataSource.setSystemThemeModeFlag(newValue); - // Future getThemeMode() async { - // final themeMode = await dataSource.getThemeMode()?? ThemeMode.system; - // } - // - // Future setThemeMode(final ThemeMode newThemeMode) => - // dataSource.setThemeMode(newThemeMode); - Future> supportedLocales() async => getSupportedLocales(); Future getActiveLocale() async { @@ -54,7 +50,7 @@ class PreferencesRepository { } // when it's null fallback on delegate locale - chosenLocale ??= await getDelegateLocale(); + chosenLocale ??= Localization.systemLocale; return chosenLocale; } @@ -63,6 +59,10 @@ class PreferencesRepository { await dataSource.setLocale(newLocale.toString()); } + Future resetActiveLocale() async { + await dataSource.setLocale(null); + } + /// true when we need to show onboarding Future getShouldShowOnboarding() async => dataSource.getOnboardingFlag(); diff --git a/lib/logic/api_maps/graphql_maps/graphql_api_map.dart b/lib/logic/api_maps/graphql_maps/graphql_api_map.dart index 6ae9fd20..d2823a56 100644 --- a/lib/logic/api_maps/graphql_maps/graphql_api_map.dart +++ b/lib/logic/api_maps/graphql_maps/graphql_api_map.dart @@ -116,7 +116,7 @@ abstract class GraphQLApiMap { ); } - String get _locale => getIt.get().localeCode ?? 'en'; + String get _locale => getIt.get().localeCode; String get _token { String token = ''; diff --git a/lib/logic/get_it/api_config.dart b/lib/logic/get_it/api_config.dart index 21f1bb17..33632f38 100644 --- a/lib/logic/get_it/api_config.dart +++ b/lib/logic/get_it/api_config.dart @@ -9,7 +9,6 @@ class ApiConfigModel { final Box _box = Hive.box(BNames.serverInstallationBox); ServerHostingDetails? get serverDetails => _serverDetails; - String? get localeCode => _localeCode; String? get serverProviderKey => _serverProviderKey; String? get serverLocation => _serverLocation; String? get serverType => _serverType; @@ -21,7 +20,12 @@ class ApiConfigModel { ServerDomain? get serverDomain => _serverDomain; BackblazeBucket? get backblazeBucket => _backblazeBucket; + static const localeCodeFallback = 'en'; String? _localeCode; + String get localeCode => _localeCode ?? localeCodeFallback; + Future setLocaleCode(final String value) async => _localeCode = value; + Future resetLocaleCode() async => _localeCode = null; + String? _serverProviderKey; String? _serverLocation; String? _dnsProviderKey; @@ -33,10 +37,6 @@ class ApiConfigModel { ServerDomain? _serverDomain; BackblazeBucket? _backblazeBucket; - Future setLocaleCode(final String value) async { - _localeCode = value; - } - Future storeServerProviderType(final ServerProviderType value) async { await _box.put(BNames.serverProvider, value); _serverProvider = value; @@ -101,7 +101,6 @@ class ApiConfigModel { } Future init() async { - _localeCode = 'en'; _serverProviderKey = _box.get(BNames.hetznerKey); _serverLocation = _box.get(BNames.serverLocation); _dnsProviderKey = _box.get(BNames.cloudFlareKey); diff --git a/lib/ui/pages/more/app_settings/language_picker.dart b/lib/ui/pages/more/app_settings/language_picker.dart index d4cb9607..ddd8dcbf 100644 --- a/lib/ui/pages/more/app_settings/language_picker.dart +++ b/lib/ui/pages/more/app_settings/language_picker.dart @@ -36,26 +36,27 @@ class _LanguagePickerDialog extends StatelessWidget { static const routeSettings = RouteSettings(name: 'LanguagePickerDialog'); @override - Widget build(final BuildContext context) => SimpleDialog( - title: Text('application_settings.language'.tr()), - children: [ - for (final locale - in InheritedAppController.of(context).supportedLocales) - RadioMenuButton( - groupValue: context.locale, - value: locale, - child: Text( - Localization.getLanguageName(locale), - style: TextStyle( - fontWeight: locale == context.locale - ? FontWeight.w800 - : FontWeight.w400, - ), + Widget build(final BuildContext context) { + final appController = InheritedAppController.of(context); + + return SimpleDialog( + title: Text('application_settings.language'.tr()), + children: [ + for (final locale in appController.supportedLocales) + RadioMenuButton( + groupValue: appController.locale, + value: locale, + child: Text( + Localization.getLanguageName(locale), + style: TextStyle( + fontWeight: locale == appController.locale + ? FontWeight.w800 + : FontWeight.w400, ), - onChanged: (final newValue) { - Navigator.of(context).pop(newValue); - }, ), - ], - ); + onChanged: (final newValue) => Navigator.of(context).pop(newValue), + ), + ], + ); + } } diff --git a/lib/ui/pages/more/app_settings/theme_picker.dart b/lib/ui/pages/more/app_settings/theme_picker.dart index 0e123045..d08371be 100644 --- a/lib/ui/pages/more/app_settings/theme_picker.dart +++ b/lib/ui/pages/more/app_settings/theme_picker.dart @@ -6,9 +6,6 @@ class _ThemePicker extends StatelessWidget { @override Widget build(final BuildContext context) { final appController = InheritedAppController.of(context); - // final themeMode = appController.themeMode; - // final bool isSystemThemeModeEnabled = themeMode == ThemeMode.system; - // final bool isDarkModeOn = themeMode == ThemeMode.dark; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -19,11 +16,6 @@ class _ThemePicker extends StatelessWidget { Text('application_settings.system_theme_mode_description'.tr()), value: appController.systemThemeModeActive, onChanged: appController.setSystemThemeModeFlag, - // onChanged: (final newValue) => appController.setThemeMode( - // newValue - // ? ThemeMode.system - // : (isDarkModeOn ? ThemeMode.dark : ThemeMode.light), - // ), ), SwitchListTile.adaptive( title: Text('application_settings.dark_theme_title'.tr()), @@ -32,11 +24,6 @@ class _ThemePicker extends StatelessWidget { onChanged: appController.systemThemeModeActive ? null : appController.setDarkThemeModeFlag, - // onChanged: isSystemThemeModeEnabled - // ? null - // : (final newValue) => appController.setThemeMode( - // newValue ? ThemeMode.dark : ThemeMode.light, - // ), ), ], ); From 05800f5900995795888594817120e4705caf631b Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 19 Jun 2024 15:12:34 +0400 Subject: [PATCH 33/37] feat: hide/show console header value button, changed icons from cupertino to material --- lib/ui/pages/more/console/console_log_item_dialog.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ui/pages/more/console/console_log_item_dialog.dart b/lib/ui/pages/more/console/console_log_item_dialog.dart index 5cb20142..e5dec45b 100644 --- a/lib/ui/pages/more/console/console_log_item_dialog.dart +++ b/lib/ui/pages/more/console/console_log_item_dialog.dart @@ -1,5 +1,4 @@ import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/models/console_log.dart'; import 'package:selfprivacy/utils/platform_adapter.dart'; @@ -270,7 +269,9 @@ class _ObscuredKeyValueRowState extends State<_ObscuredKeyValueRow> { ), IconButton( icon: Icon( - _obscureValue ? CupertinoIcons.eye : CupertinoIcons.eye_slash, + _obscureValue + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, ), onPressed: () { _obscureValue ^= true; // toggle value From dd036890b26ee81ee3763b2e9c12d218a995b87a Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 19 Jun 2024 15:20:15 +0400 Subject: [PATCH 34/37] fix: l10n assets format fix, whitespace and keyname --- assets/translations/be.json | 2 +- assets/translations/en.json | 16 ++++++++-------- lib/logic/models/console_log.dart | 2 +- lib/ui/pages/more/console/console_page.dart | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/assets/translations/be.json b/assets/translations/be.json index 4c85255b..49972ea9 100644 --- a/assets/translations/be.json +++ b/assets/translations/be.json @@ -250,7 +250,7 @@ "dark_theme_title": "Цёмная тэма", "change_application_theme": "Змяніць каляровую тэму", "language": "Мова", - "click_to_change_locale":"Націсніце, каб адчыніць меню выбару мовы", + "click_to_change_locale": "Націсніце, каб адчыніць меню выбару мовы", "dangerous_settings": "Небяспечныя налады", "reset_config_title": "Скід налад", "reset_config_description": "Скінуць API ключы i суперкарыстальніка." diff --git a/assets/translations/en.json b/assets/translations/en.json index 302cd6fe..9f672acd 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -49,16 +49,16 @@ "waiting": "Waiting for initialization…", "copy": "Copy", "copy_raw": "Raw response", - "historyEmpty": "No data yet", - "error":"Error", - "log":"Log", - "rest_api_request":"Rest API Request", - "rest_api_response":"Rest API Response", - "graphql_request":"GraphQL Request", - "graphql_response":"GraphQL Response", + "history_empty": "No data yet", + "error": "Error", + "log": "Log", + "rest_api_request": "Rest API Request", + "rest_api_response": "Rest API Response", + "graphql_request": "GraphQL Request", + "graphql_response": "GraphQL Response", "logged_at": "Logged at", "data": "Data", - "errors":"Errors", + "errors": "Errors", "error_path": "Path", "error_locations": "Locations", "error_extensions": "Extensions", diff --git a/lib/logic/models/console_log.dart b/lib/logic/models/console_log.dart index 91cef018..e928b04c 100644 --- a/lib/logic/models/console_log.dart +++ b/lib/logic/models/console_log.dart @@ -29,7 +29,7 @@ sealed class ConsoleLog { String get content; /// data available for copy in dialog - String? get shareableData => '{"title":"$title",\n' + String? get shareableData => '{"title": "$title",\n' '"timestamp": "$fullUTCString",\n' '"data":{\n$content\n}' '\n}'; diff --git a/lib/ui/pages/more/console/console_page.dart b/lib/ui/pages/more/console/console_page.dart index 95b77d73..e69eb2fe 100644 --- a/lib/ui/pages/more/console/console_page.dart +++ b/lib/ui/pages/more/console/console_page.dart @@ -116,7 +116,7 @@ class _ConsoleViewEmpty extends StatelessWidget { @override Widget build(final BuildContext context) => Align( alignment: Alignment.topCenter, - child: Text('console_page.historyEmpty'.tr()), + child: Text('console_page.history_empty'.tr()), ); } From 99a9e5bfed2c031ee31ca05b3d7ca53da3069b6c Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 19 Jun 2024 16:06:49 +0400 Subject: [PATCH 35/37] fix: translate server settings page title --- lib/ui/router/router.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ui/router/router.dart b/lib/ui/router/router.dart index 14f09d82..44bcd3de 100644 --- a/lib/ui/router/router.dart +++ b/lib/ui/router/router.dart @@ -144,6 +144,8 @@ String getRouteTitle(final String routeName) { return 'domain.screen_title'; case 'ServerDetailsRoute': return 'server.card_title'; + case 'ServerSettingsRoute': + return 'server.settings'; case 'BackupDetailsRoute': return 'backup.card_title'; case 'BackupsListRoute': From 82a606e320fa424fa8d0455cbd76efeccc33218d Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 19 Jun 2024 16:07:36 +0400 Subject: [PATCH 36/37] fix: removed horizontal dividers from app_settings_page --- lib/ui/pages/more/app_settings/app_settings.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/ui/pages/more/app_settings/app_settings.dart b/lib/ui/pages/more/app_settings/app_settings.dart index ae67c825..be3df075 100644 --- a/lib/ui/pages/more/app_settings/app_settings.dart +++ b/lib/ui/pages/more/app_settings/app_settings.dart @@ -35,12 +35,10 @@ class _AppSettingsPageState extends State { _ThemePicker( key: ValueKey('theme_picker'.tr()), ), - const Divider(height: 5, thickness: 0), _LanguagePicker( key: ValueKey('language_picker'.tr()), ), - const Divider(height: 5, thickness: 0), - const Gap(4), + const Gap(8), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( From 3fc3a6e7f464cb24110c8d4f14b1cb5469df214b Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 19 Jun 2024 18:01:13 +0400 Subject: [PATCH 37/37] feat: doc comment with clarification of console_logs blacklistedHeaders and hideList --- lib/logic/models/console_log.dart | 3 +++ lib/ui/pages/more/console/console_log_item_dialog.dart | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/lib/logic/models/console_log.dart b/lib/logic/models/console_log.dart index e928b04c..bd3eda8c 100644 --- a/lib/logic/models/console_log.dart +++ b/lib/logic/models/console_log.dart @@ -71,6 +71,9 @@ class RestApiRequestConsoleLog extends ConsoleLog { super.severity, }); + /// headers thath should not be included into clipboard buffer, as opposed to + /// `[[ConsoleLogItemDialog]]` `_KeyValueRow.hideList` which filters values, + /// that should be accessible from UI, but hidden in screenshots static const blacklistedHeaders = ['Authorization']; final String? method; diff --git a/lib/ui/pages/more/console/console_log_item_dialog.dart b/lib/ui/pages/more/console/console_log_item_dialog.dart index e5dec45b..dc5d55cd 100644 --- a/lib/ui/pages/more/console/console_log_item_dialog.dart +++ b/lib/ui/pages/more/console/console_log_item_dialog.dart @@ -202,6 +202,10 @@ class _SectionRow extends StatelessWidget { class _KeyValueRow extends StatelessWidget { const _KeyValueRow(this.title, this.value); + /// headers thath should be hidden in screenshots, but still accessible for + /// user, as opposed to `[[ConsoleLog]]` + /// `RestApiRequestConsoleLog.blacklistedHeaders` which need to be filtered + /// out from clipboard content static const List hideList = ['Authorization']; final String title;