diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f0db9a6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +Choose template: + - [General](?expand=1&template=pull_request_general_template.md) + - [Bug](?expand=1&template=pull_request_bug_template.md) + - [Release](?expand=1&template=pull_request_release_template.md) + \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_bug_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_bug_template.md new file mode 100644 index 0000000..a215efd --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_bug_template.md @@ -0,0 +1,20 @@ +### Description +*Please explain the changes you made here.* + +### Checklist +- [ ] Check your code additions will fail neither code linting checks nor unit test. +- [ ] Associated issues have been linked. +- [ ] Performed a self-review of my own code. + +### What is the current behavior? +*Please describe the current behavior that you are modifying.* + +### What is the new behavior? +*Please describe the behavior or changes that are being added by this PR.* + +### Where has this been tested? + - **Device:** (e.g. Asus Zenfone 11) + - **Android Version:** (e.g. Android 16) + +### Further comments +If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_general_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_general_template.md new file mode 100644 index 0000000..e81226c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_general_template.md @@ -0,0 +1,18 @@ +### Description +*Please explain the changes you made here.* + +### Checklist +- [ ] Check your code additions will fail neither code linting checks nor unit test. +- [ ] Associated issues have been linked. +- [ ] Performed a self-review of my own code. + +### Does this introduce a breaking change? +- [ ] Yes +- [ ] No + +### Where has this been tested? + - **Device:** (e.g. Asus Zenfone 11) + - **Android Version:** (e.g. Android 16) + +### Further comments +If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_release_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_release_template.md new file mode 100644 index 0000000..787f598 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_release_template.md @@ -0,0 +1,9 @@ +### Breaking Changes + +### New Features + +### Bug Fixes + +### Performance Improvements + +### Other Changes \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f9f5f60 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,99 @@ +## [1.1.0-beta](https://github.com/DrSolidDevil/Vidar/compare/v1.0.3-beta...v1.1.0-beta) (2025-07-13) + +> This version of Vidar introduces color sets! You can now choose how your experience will look. In addition to color sets, now you can export logs at any time through the settings. This version also finally introduces the ability to see the time a message was sent/received. + +### New Features +* Color sets for Vidar currently there are four color sets (including default). [`642c3cb`](https://github.com/DrSolidDevil/Vidar/commit/642c3cb95b271b4a59e451042c7fba6dbc9c8198) [`7c8b6a2`](https://github.com/DrSolidDevil/Vidar/commit/7c8b6a27091b0fcbcef068cbdcfd49f73d7b81bc) [`1cd2a28`](https://github.com/DrSolidDevil/Vidar/commit/1cd2a281deba7977c7dceef8990b148d243d6e13) [`0aea5e9`](https://github.com/DrSolidDevil/Vidar/commit/0aea5e9f067f290435c84f7c09a9084c10cb3aa5) +* Ability to export logs at any time using a button on the settings page. [`9ae93a9`](https://github.com/DrSolidDevil/Vidar/commit/9ae93a9304b2644da57b72c15ee4e7101f639341) +* Messages now display the time it was sent/received. [`beadd53`](https://github.com/DrSolidDevil/Vidar/commit/beadd532267ba8660ea0b7e61c91194838a467c1) +* Edit contact page now shows current name, phone number and optionally the encryption key (by default not shown) [`004cb38`](https://github.com/DrSolidDevil/Vidar/commit/004cb38036a8227c8a5c064a7e39515c6a0b9cf9) + +### Bug Fixes +* Fixed faulty save logic for edit_contact.dart. [`1a336aa`](https://github.com/DrSolidDevil/Vidar/commit/1a336aaea631f39e64b88e62519036bc3ac796a9) +* Fixed SMS dates when retrieving from map sent via method channel. [`beadd53`](https://github.com/DrSolidDevil/Vidar/commit/beadd532267ba8660ea0b7e61c91194838a467c1) + +### Other Changes +* Added pull-request templates. [`c2a31a3`](https://github.com/DrSolidDevil/Vidar/commit/c2a31a3516d4918a4a047b59079074dbfad05646) +* Created changelog. [`3e8177c`](https://github.com/DrSolidDevil/Vidar/commit/3e8177cd76368379a5eb42b556e45deeac08435d) +* Changed keyboard type of the phone number field on the edit contact page to a phone number optimized one. [`6b71f84`](https://github.com/DrSolidDevil/Vidar/commit/6b71f84ada9b4e4b174ae4c49878e8f5231752aa) +* Navigation standardization. [`ce58482`](https://github.com/DrSolidDevil/Vidar/commit/ce58482f1f167ccb4718b28444523155de199212) [`4087354`](https://github.com/DrSolidDevil/Vidar/commit/4087354c3840b4233f2201550ac0c0f349b06e89) +* Dialog to export logs no longer utilizes ErrorPopup [`8eb897a`](https://github.com/DrSolidDevil/Vidar/commit/8eb897a9eca87de5edaf9e0834f45f8348003409) +* Rewrote logging in an effort to improve DX. [`4a9881f`](https://github.com/DrSolidDevil/Vidar/commit/4a9881f6c5a270f651fe7e9b80fe33a40c0b2c56) [`f013694`](https://github.com/DrSolidDevil/Vidar/commit/f013694183178e95691b375165db0d9631248e8c) [`ccd64bf`](https://github.com/DrSolidDevil/Vidar/commit/ccd64bfcd92ff13739b4dbeab8b6c51426ee1f7d) +* Removed unused packages. [`51d0fbe`](https://github.com/DrSolidDevil/Vidar/commit/51d0fbe2698d26d2765c7859acebb809f0587ca8) +* Removed linter rule "avoid_catches_without_on_clauses" [`6663e48`](https://github.com/DrSolidDevil/Vidar/commit/6663e4875c569440df35afb8933c5ef4be1fea2b) +* Minor visual enhancements [`f7c8f78`](https://github.com/DrSolidDevil/Vidar/commit/f7c8f78eac044fb0e6e51a80660fa16c99bf8582) +* Typo fixed. [`9aca116`](https://github.com/DrSolidDevil/Vidar/commit/9aca116fb4da0046f514e83219efbe55d646a6a8) + +
+ +## [1.0.3-beta](https://github.com/DrSolidDevil/Vidar/compare/v1.0.2-beta...v1.0.3-beta) (2025-07-10) + +> No real changes for the end user; upgrading is not necessary. + +### Other Changes +* Change license from 3-Clause BSD to Mozilla Public License Version 2.0 [`87bed3f`](https://github.com/DrSolidDevil/Vidar/commit/87bed3ffa831e00e8a6e235bb8129a0292bd92b6) +* Added version validation for pushing to main. [`c259588`](https://github.com/DrSolidDevil/Vidar/commit/c2595885e4b817f1c397bba621fef352911f4322) + +
+ +## [1.0.2-beta](https://github.com/DrSolidDevil/Vidar/compare/v1.0.1-beta...v1.0.2-beta) (2025-07-09) + +> Very minor changes relating to some refactoring and logging. No real changes for the end user; upgrading is not necessary. + +### Other Changes +* Made encryption error messages public and made message_bar.dart use these when checking for specific error type in switch. [`8315d69`](https://github.com/DrSolidDevil/Vidar/commit/8315d69f60a2fafc2e6bf924f12d846cb2b6dd71) +* Added info to log in encryptMessage when no key exists, informing if it is allowed or not. [`de0478d`](https://github.com/DrSolidDevil/Vidar/commit/de0478d16ce98aff72281f64a752e21ae1ebad4e) + +
+ +## [1.0.1-beta](https://github.com/DrSolidDevil/Vidar/compare/v1.0.0-beta...v1.0.1-beta) (2025-07-08) + +> Minor patch relating to editing existing contacts and errors shown in the message bar. + +### Bug Fixes +* Fixed error widget from message bar not adapting to changes in viewinsets (i.e. keyboard). [`c9bc4b9`](https://github.com/DrSolidDevil/Vidar/commit/c9bc4b91c5e586df7a33b3244028f5fb398a0780) +* Fixed displaying of error widget when transitioning from keyboard being up to it being down or vice versa. [`c9bc4b9`](https://github.com/DrSolidDevil/Vidar/commit/c9bc4b91c5e586df7a33b3244028f5fb398a0780) + +### Other Changes +* If a user forgets to enter a plus sign before the phone number it automatically adds it. [`a5f4980`](https://github.com/DrSolidDevil/Vidar/commit/a5f49804b6a098089a340949bb560215330c184d) +* Editing existing contact's details is now subject to a check of contact information invalidity. [`a5f4980`](https://github.com/DrSolidDevil/Vidar/commit/a5f49804b6a098089a340949bb560215330c184d) +* Display time for errors related to sending messages and the message bar is extended to 15 seconds. [`c9bc4b9`](https://github.com/DrSolidDevil/Vidar/commit/c9bc4b91c5e586df7a33b3244028f5fb398a0780) + +
+ +## [1.0.0-beta](https://github.com/DrSolidDevil/Vidar/compare/v0.1.0...v1.0.0-beta) (2025-07-07) + +> This release marks a major milestone in Vidar's development. While still in beta, it introduces a wide range of new features, performance improvements, and changes that bring Vidar closer to a stable 1.0 release. + +### Breaking Changes +* Made key storage encrypted using [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage). [`489d3af`](https://github.com/DrSolidDevil/Vidar/commit/489d3af688163e74ca035663ed842b2bfb712288) +* APK package name renamed from `com.vidar.vidar` to `com.drsoliddevil.vidar`. This may affect upgrades and backups. [`2bd9cae`](https://github.com/DrSolidDevil/Vidar/commit/2bd9caee388cdc80218a83908ac7a4872d05ba76) + + +### New Features +* Ability to delete contacts from contact list by holding on the contact badge (i.e. long press). [`42f5681`](https://github.com/DrSolidDevil/Vidar/commit/42f5681ca3140c1b021c72dd6f3ace0f87726775) +* Loading screen while you load a conversation. [`be5e0b9`](https://github.com/DrSolidDevil/Vidar/commit/be5e0b9ed0bfbda1ea01014b00c7d297ae541368) +* Ability to wipe all keys with one button (in settings). [`cd466ea`](https://github.com/DrSolidDevil/Vidar/commit/cd466eae30f804ad5e1afd9be6bf734699660792) +* System for logging with ability to export logs. [`33d1972`](https://github.com/DrSolidDevil/Vidar/commit/33d1972c4e7393860a19a2b2aeaba958f69adf05) [`95f4a7d`](https://github.com/DrSolidDevil/Vidar/commit/95f4a7d1c01fa282bfedc02500ecb9c5671cac4b) [`089dcd9`](https://github.com/DrSolidDevil/Vidar/commit/089dcd91ab6d52647888ae1b0517081770d5c0db) [`004c51d`](https://github.com/DrSolidDevil/Vidar/commit/004c51df68b2e33748c43915bf9c3ddf243931cf) + +### Bug Fixes +* Fixed phone number parsing only applying to new contact. [`becca8a`](https://github.com/DrSolidDevil/Vidar/commit/becca8ae7f94f8f66ec7b237769b882df456182b) +* Fixed SmsReceiver and SmsNotifier. [`7a6ede2`](https://github.com/DrSolidDevil/Vidar/commit/7a6ede25e8e04196d0c9d22ebe719049de034468) [`579a46d`](https://github.com/DrSolidDevil/Vidar/commit/579a46dcd5919782e3bcd2879661af8df5a12ac9) [`6629316`](https://github.com/DrSolidDevil/Vidar/commit/662931697ed4070d22bb80950e0ece5e3e92613f) + +### Performance Improvements +* _generateRandomChar or _generateUniqueRandomInt does not need to create a new instance of Random every time they are called (if a Random object is provided via parameters). [`e9f8355`](https://github.com/DrSolidDevil/Vidar/commit/e9f8355e9fa0eb0b6e730b97a989697091351775) +* decryptMessage does not need to create a new instance of AesGcm every time they are called (if a AesGcm object is provided via parameters). [`d47ebb8`](https://github.com/DrSolidDevil/Vidar/commit/d47ebb825653ad99f183cd4d90a22098c5c1f326) + +### Other Changes +* Rewritten readme. [`5e33c19`](https://github.com/DrSolidDevil/Vidar/commit/5e33c19f4a9d61d294e678f68249639c5fc8a1d9) +* Created a security policy. [`6c8dd32`](https://github.com/DrSolidDevil/Vidar/commit/6c8dd326d5da617fcbe9fbeeaf285fc0350e7313) [`ff7afa0`](https://github.com/DrSolidDevil/Vidar/commit/ff7afa0ba4fc3cb1611047bc20544eba36d41d3f) [`1c30221`](https://github.com/DrSolidDevil/Vidar/commit/1c30221b5e9b201a743ef783e44fb19b8309cdf4) +* Created code of conduct (Contributor Covenant). [`f66ca46`](https://github.com/DrSolidDevil/Vidar/commit/f66ca46bc11f667c8541678ca7fab1f92c1a8f62) [`db2e83e`](https://github.com/DrSolidDevil/Vidar/commit/db2e83e6698d18eec29f9574c3f27d71437006dc) +* Created workflows for building APK on new release and running linter before merge into main. [`fb4d988`](https://github.com/DrSolidDevil/Vidar/commit/fb4d988dc35944fd2930a07adc2ddfc99786d1a8) +* Removed web-related code & files. [`130f03e`](https://github.com/DrSolidDevil/Vidar/commit/130f03e70e743318a50a2f20e76bc56aa48f8c40) +* Messages with STATUS_FAILED will now be displayed as "MESSAGE_FAILED" in the conversation. [`bccae1c`](https://github.com/DrSolidDevil/Vidar/commit/bccae1c571472ed84238cc6db06b68db8efad7e7) +* Message bar clears when a message is sent (without error). [`554e62c`](https://github.com/DrSolidDevil/Vidar/commit/554e62ce03b908a71b1fef1d3345a85133af89d4) +* Chat updates when you send a message. [`110b2e2`](https://github.com/DrSolidDevil/Vidar/commit/110b2e2611224218c3e3028205e279d0c7dffbd4) +* Buttons redesigned using TextButton. [`f3de296`](https://github.com/DrSolidDevil/Vidar/commit/f3de29691a4ff1e054f6e90d1916c6bcce236432) +* Minor visual enhancements [`2540ad7`](https://github.com/DrSolidDevil/Vidar/commit/2540ad751c03dce39885fbb5b36551a3325a0962) [`fe025ee`](https://github.com/DrSolidDevil/Vidar/commit/fe025ee8cec4edd8eeb5ee0be77886747678fa97) [`454ce0c`](https://github.com/DrSolidDevil/Vidar/commit/454ce0cae7b3ddf7dfe74cbdf3344d227671de74) +* Fixed typos. [`34a7a4a`](https://github.com/DrSolidDevil/Vidar/commit/34a7a4a4f2857c363fb0c5ee51c665a92f4a73c0) [`cd820ed`](https://github.com/DrSolidDevil/Vidar/commit/cd820edfd1927d5be5ad7bafcd83491c53deb695) [`f9fd354`](https://github.com/DrSolidDevil/Vidar/commit/f9fd3546b8b302165e051aacad63081b6504f579 ) + diff --git a/analysis_options.yaml b/analysis_options.yaml index b006f9f..d25f3cf 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -20,7 +20,6 @@ linter: - annotate_overrides - annotate_redeclares - avoid_bool_literals_in_conditional_expressions - - avoid_catches_without_on_clauses - avoid_catching_errors - avoid_empty_else - avoid_escaping_inner_quotes diff --git a/lib/configuration.dart b/lib/configuration.dart index 74373f3..2de75ba 100644 --- a/lib/configuration.dart +++ b/lib/configuration.dart @@ -1,21 +1,10 @@ -import "package:flutter/material.dart"; +import "package:device_info_plus/device_info_plus.dart" show AndroidDeviceInfo; import "package:logging/logging.dart"; +import "package:package_info_plus/package_info_plus.dart" show PackageInfo; -class VidarColors { - /// #1a1c28 - static const Color primaryDarkSpaceCadet = Color.fromARGB(255, 26, 28, 40); - - /// #64007b - static const Color secondaryMetallicViolet = Color.fromARGB(255, 53, 22, 100); - - /// #b18c19 - static const Color tertiaryGold = Color.fromARGB(255, 177, 140, 25); - - /// #140627 - static const Color extraMidnightPurple = Color.fromARGB(255, 20, 6, 39); - - /// #b22222 - static const Color extraFireBrick = Color.fromARGB(255, 178, 34, 34); +void externalConfiguration() { + // Required to be true if you want to change log level of loggers (e.g. allow config messages). + hierarchicalLoggingEnabled = true; } class CryptographicConfiguration { @@ -48,12 +37,16 @@ class TimeConfiguration { class MiscellaneousConfiguration { static const String errorPrefix = "⚠"; + static const List messageHints = [ + "Write them a message!", + "Show them some love ❤️", + "Tell them your secrets 👀", + "Start gossiping...", + "Talk to them, they miss you.", + ]; } class LoggingConfiguration { - /// If encryption errors should be logged/printed - static const bool verboseEncryptionError = false; - static const String loggerName = "VidarLogger"; static const bool extraVerboseLogs = true; @@ -65,10 +58,65 @@ class LoggingConfiguration { } static String verboseLogMessage(final LogRecord log) { - return "\n${log.sequenceNumber}: ${log.time.toIso8601String()}\nLevel: ${log.level.name}\nError: ${log.error}\nMessage: ${log.message}\nStack Trace: ${log.stackTrace}"; + return """ +[${log.sequenceNumber}] ${log.time.toIso8601String()} +\tLevel: ${log.level.name} +\tError: ${log.error} +\tMessage: ${log.message.replaceAll("\n", "\n\t")} +\tStack Trace: ${log.stackTrace} + """; + } + + static String normalLogMessage(final LogRecord log) { + return """ +[${log.sequenceNumber}] ${log.time.toIso8601String()} +\tLevel: ${log.level.name} +\tError: ${log.error} +\tMessage: ${log.message.replaceAll("\n", "\n\t")} + """; } static String conciseLogMessage(final LogRecord log) { - return "\n${log.sequenceNumber}: ${log.time.toIso8601String()}\nLevel: ${log.level.name}\nMessage: ${log.message}"; + return """ +[${log.sequenceNumber}] ${log.time.toIso8601String()} +\tLevel: ${log.level.name} +\tMessage: ${log.message.replaceAll("\n", "\n\t")} + """; + } + + static String minimalLogMessage(final LogRecord log) { + return """ +[${log.sequenceNumber}] ${log.time.toIso8601String()} +\t${log.message.replaceAll("\n", "\n\t")} + """; + } + + static String initLog({ + required final PackageInfo packageInfo, + required final AndroidDeviceInfo deviceInfo, + }) { + return """ +========== APP INFO ========== +App Name: ${packageInfo.appName} +Version: ${packageInfo.version} +Build Number: ${packageInfo.buildNumber} +======== DEVICE INFO ========= +Base OS: ${deviceInfo.version.baseOS == "" ? "unknown" : deviceInfo.version.baseOS} +Bootloader: ${deviceInfo.bootloader} +Total RAM: ${deviceInfo.physicalRamSize} MB +Release ${deviceInfo.version.release} +SDK: ${deviceInfo.version.sdkInt} +Security Patch: ${deviceInfo.version.securityPatch} +Manufacturer: ${deviceInfo.manufacturer} +Brand: ${deviceInfo.brand} +Model: ${deviceInfo.model} +Emulator: ${!deviceInfo.isPhysicalDevice} +Hardware Keystore: ${deviceInfo.systemFeatures.contains("android.hardware.hardware_keystore")} +Telephony: ${deviceInfo.systemFeatures.contains("android.hardware.telephony")} +Telephony Messaging: ${deviceInfo.systemFeatures.contains("android.hardware.telephony.messaging")} +Telephony Subscription: ${deviceInfo.systemFeatures.contains("android.hardware.telephony.subscription")} +Any Camera: ${deviceInfo.systemFeatures.contains("android.hardware.camera.any")} +============================== + """; } } diff --git a/lib/main.dart b/lib/main.dart index c17cf59..6ddf73c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,9 @@ import "package:flutter/material.dart"; import "package:permission_handler/permission_handler.dart"; +import "package:vidar/configuration.dart"; import "package:vidar/pages/contact_list.dart"; import "package:vidar/pages/no_sms_permission.dart"; import "package:vidar/utils/common_object.dart"; -import "package:vidar/utils/contact.dart"; -import "package:vidar/utils/settings.dart"; import "package:vidar/utils/sms.dart"; import "package:vidar/utils/storage.dart"; @@ -13,12 +12,9 @@ late PermissionStatus smsStatus; void main() async { WidgetsFlutterBinding.ensureInitialized(); - final ContactList contactList = ContactList([]); - final Settings settings = Settings(); - CommonObject.contactList = contactList; - CommonObject.settings = settings; + externalConfiguration(); - await loadData(contactList, settings); + await loadData(CommonObject.contactList, CommonObject.settings); SmsConstants(await retrieveSmsConstantsMap()); smsStatus = await Permission.sms.request(); diff --git a/lib/pages/chat.dart b/lib/pages/chat.dart index 675bc8f..ceabb31 100644 --- a/lib/pages/chat.dart +++ b/lib/pages/chat.dart @@ -1,9 +1,9 @@ import "package:flutter/material.dart"; -import "package:vidar/configuration.dart"; import "package:vidar/pages/contact_list.dart"; import "package:vidar/pages/edit_contact.dart"; import "package:vidar/utils/contact.dart"; import "package:vidar/utils/navigation.dart"; +import "package:vidar/utils/settings.dart"; import "package:vidar/widgets/conversation_widget.dart"; import "package:vidar/widgets/message_bar.dart"; @@ -28,12 +28,12 @@ class _ChatPageState extends State { @override Widget build(final BuildContext context) => Scaffold( appBar: AppBar( - backgroundColor: VidarColors.secondaryMetallicViolet, + backgroundColor: Settings.colorSet.secondary, title: Text( contact.name, - style: const TextStyle( + style: TextStyle( fontSize: 18, - color: Colors.white, + color: Settings.colorSet.text, decoration: TextDecoration.none, ), ), @@ -44,7 +44,7 @@ class _ChatPageState extends State { onPressed: () { clearNavigatorAndPush(context, const ContactListPage()); }, - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: Icon(Icons.arrow_back, color: Settings.colorSet.text), tooltip: "Go back", ), ), @@ -59,7 +59,7 @@ class _ChatPageState extends State { EditContactPage(contact, ContactPageCaller.chatPage), ); }, - icon: const Icon(Icons.edit, color: Colors.white), + icon: Icon(Icons.edit, color: Settings.colorSet.text), tooltip: "Edit", ), ), diff --git a/lib/pages/contact_list.dart b/lib/pages/contact_list.dart index 421030e..db2802e 100644 --- a/lib/pages/contact_list.dart +++ b/lib/pages/contact_list.dart @@ -1,10 +1,10 @@ import "package:flutter/material.dart"; -import "package:vidar/configuration.dart"; import "package:vidar/pages/edit_contact.dart"; import "package:vidar/pages/settings_page.dart"; import "package:vidar/utils/common_object.dart"; import "package:vidar/utils/contact.dart"; import "package:vidar/utils/navigation.dart"; +import "package:vidar/utils/settings.dart"; class ContactListPage extends StatefulWidget { const ContactListPage({super.key}); @@ -24,15 +24,17 @@ class _ContactListPageState extends State { @override Widget build(final BuildContext context) { return Scaffold( - backgroundColor: VidarColors.secondaryMetallicViolet, + backgroundColor: Settings.colorSet.secondary, floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, floatingActionButton: DecoratedBox( decoration: BoxDecoration( - color: VidarColors.secondaryMetallicViolet, - border: Border.all(color: Colors.white, width: 2), + color: Settings.colorSet.floatingActionButton, + border: Border.all(color: Settings.colorSet.text, width: 2), borderRadius: BorderRadius.circular(10), ), child: FloatingActionButton( + elevation: 0, + highlightElevation: 0, onPressed: () { final Contact newContact = Contact("", "", ""); clearNavigatorAndPush( @@ -41,7 +43,7 @@ class _ContactListPageState extends State { ); }, backgroundColor: Colors.transparent, - foregroundColor: Colors.white, + foregroundColor: Settings.colorSet.text, child: const Icon(Icons.add_comment), ), ), @@ -59,12 +61,12 @@ class _ContactListPageState extends State { ), appBar: AppBar( - backgroundColor: VidarColors.primaryDarkSpaceCadet, - title: const Text( + backgroundColor: Settings.colorSet.primary, + title: Text( "Vidar - For Privacy's Sake", style: TextStyle( fontSize: 18, - color: Colors.white, + color: Settings.colorSet.text, decoration: TextDecoration.none, ), ), @@ -75,7 +77,7 @@ class _ContactListPageState extends State { onPressed: () { clearNavigatorAndPush(context, const SettingsPage()); }, - icon: const Icon(Icons.settings, color: Colors.white), + icon: Icon(Icons.settings, color: Settings.colorSet.text), tooltip: "Settings", ), ), diff --git a/lib/pages/edit_contact.dart b/lib/pages/edit_contact.dart index a98fe3f..186f0ff 100644 --- a/lib/pages/edit_contact.dart +++ b/lib/pages/edit_contact.dart @@ -24,7 +24,7 @@ class _EditContactPageState extends State { _EditContactPageState(); late Contact contact; late ContactPageCaller caller; - final TextEditingController encryptionKeyController = TextEditingController(); + late final TextEditingController encryptionKeyController; String? newName; String? newKey; @@ -35,6 +35,11 @@ class _EditContactPageState extends State { super.initState(); contact = widget.contact; caller = widget.caller; + encryptionKeyController = TextEditingController( + text: Settings.showEncryptionKeyInEditContact + ? contact.encryptionKey + : null, + ); } @override @@ -87,8 +92,6 @@ class _EditContactPageState extends State { newPhoneNumber = contact.phoneNumber; } - saveData(CommonObject.contactList, CommonObject.settings); - switch (caller) { case ContactPageCaller.chatPage: if (isInvalidContactByParams(newName, newKey, newPhoneNumber)) { @@ -118,6 +121,7 @@ class _EditContactPageState extends State { contact.name = newName!; contact.encryptionKey = newKey!; contact.phoneNumber = newPhoneNumber!; + saveData(CommonObject.contactList, CommonObject.settings); clearNavigatorAndPush(context, ChatPage(contact)); } @@ -152,12 +156,17 @@ class _EditContactPageState extends State { if (Settings.keepLogs) { if (success) { if (LoggingConfiguration.extraVerboseLogs) { + contact.name = newName!; + contact.encryptionKey = newKey!; + contact.phoneNumber = newPhoneNumber!; + saveData(CommonObject.contactList, CommonObject.settings); CommonObject.logger!.info( "New contact ${contact.uuid} has been saved", ); + clearNavigatorAndPush(context, const ContactListPage()); } } else { - CommonObject.logger!.warning( + CommonObject.logger!.info( "Failed to add contact ${contact.uuid}", ); } @@ -177,6 +186,7 @@ class _EditContactPageState extends State { contact.name = newName!; contact.encryptionKey = newKey!; contact.phoneNumber = newPhoneNumber!; + saveData(CommonObject.contactList, CommonObject.settings); clearNavigatorAndPush(context, const ContactListPage()); } } @@ -187,7 +197,7 @@ class _EditContactPageState extends State { Widget build(final BuildContext context) { return Container( margin: const EdgeInsets.only(top: 30), - color: VidarColors.primaryDarkSpaceCadet, + color: Settings.colorSet.primary, child: Column( children: [ Column( @@ -200,25 +210,29 @@ class _EditContactPageState extends State { bottom: 30, ), child: Material( - color: VidarColors.primaryDarkSpaceCadet, + color: Settings.colorSet.primary, child: Container( height: 50, decoration: BoxDecoration( - color: VidarColors.secondaryMetallicViolet, + color: Settings.colorSet.secondary, borderRadius: BorderRadius.circular(7), border: Border.all(color: Colors.transparent), ), padding: const EdgeInsets.only(left: 10), child: TextField( + controller: TextEditingController(text: contact.name), decoration: InputDecoration( hintText: "Name", - hintStyle: const TextStyle(color: Colors.white), + hintStyle: TextStyle(color: Settings.colorSet.text), border: OutlineInputBorder( borderRadius: BorderRadius.circular(7), borderSide: BorderSide.none, ), ), - style: const TextStyle(color: Colors.white, fontSize: 12), + style: TextStyle( + color: Settings.colorSet.text, + fontSize: 12, + ), onChanged: (final String value) { newName = value; }, @@ -241,11 +255,11 @@ class _EditContactPageState extends State { 160, // -100 for margins, -50 for button and -10 for button margin height: 50, child: Material( - color: VidarColors.primaryDarkSpaceCadet, + color: Settings.colorSet.primary, child: Container( height: 50, decoration: BoxDecoration( - color: VidarColors.secondaryMetallicViolet, + color: Settings.colorSet.secondary, borderRadius: BorderRadius.circular(7), border: Border.all(color: Colors.transparent), ), @@ -255,8 +269,8 @@ class _EditContactPageState extends State { decoration: InputDecoration( hintText: "Encryption Key${Settings.allowUnencryptedMessages ? ", 0=No Key" : ""}", - hintStyle: const TextStyle( - color: Colors.white, + hintStyle: TextStyle( + color: Settings.colorSet.text, fontSize: 12, ), border: OutlineInputBorder( @@ -264,7 +278,7 @@ class _EditContactPageState extends State { borderSide: BorderSide.none, ), ), - style: const TextStyle(color: Colors.white), + style: TextStyle(color: Settings.colorSet.text), onChanged: (final String value) { newKey = value; }, @@ -283,15 +297,14 @@ class _EditContactPageState extends State { encryptionKeyController.text = newKey ?? ""; }, style: IconButton.styleFrom( - backgroundColor: - VidarColors.secondaryMetallicViolet, + backgroundColor: Settings.colorSet.secondary, shape: RoundedRectangleBorder( borderRadius: BorderRadiusGeometry.circular(7), ), ), - icon: const Icon( + icon: Icon( Icons.change_circle_outlined, - color: Colors.white, + color: Settings.colorSet.text, ), ), ), @@ -308,25 +321,32 @@ class _EditContactPageState extends State { bottom: 30, ), child: Material( - color: VidarColors.primaryDarkSpaceCadet, + color: Settings.colorSet.primary, child: Container( height: 50, decoration: BoxDecoration( - color: VidarColors.secondaryMetallicViolet, + color: Settings.colorSet.secondary, borderRadius: BorderRadius.circular(7), border: Border.all(color: Colors.transparent), ), padding: const EdgeInsets.only(left: 10), child: TextField( + controller: TextEditingController( + text: contact.phoneNumber, + ), + keyboardType: TextInputType.phone, decoration: InputDecoration( hintText: "Phone Number (international)", - hintStyle: const TextStyle(color: Colors.white), + hintStyle: TextStyle(color: Settings.colorSet.text), border: OutlineInputBorder( borderRadius: BorderRadius.circular(7), borderSide: BorderSide.none, ), ), - style: const TextStyle(color: Colors.white, fontSize: 12), + style: TextStyle( + color: Settings.colorSet.text, + fontSize: 12, + ), onChanged: (final String value) { newPhoneNumber = value; }, @@ -343,15 +363,16 @@ class _EditContactPageState extends State { children: [ BasicButton( buttonText: "Discard", - textColor: Colors.white, - buttonColor: VidarColors.secondaryMetallicViolet, + textColor: Settings.colorSet.text, + buttonColor: Settings.colorSet.secondary, onPressed: discard, ), BasicButton( buttonText: "Save", - textColor: Colors.white, - buttonColor: VidarColors.tertiaryGold, + textColor: Settings.colorSet.text, + buttonColor: Settings.colorSet.tertiary, onPressed: save, + fontWeight: FontWeight.bold, ), ], ), diff --git a/lib/pages/no_sms_permission.dart b/lib/pages/no_sms_permission.dart index a6c4e8e..2761be2 100644 --- a/lib/pages/no_sms_permission.dart +++ b/lib/pages/no_sms_permission.dart @@ -1,5 +1,5 @@ import "package:flutter/material.dart"; -import "package:vidar/configuration.dart"; +import "package:vidar/utils/settings.dart"; class NoSmsPermissionPage extends StatelessWidget { const NoSmsPermissionPage({super.key}); @@ -7,14 +7,14 @@ class NoSmsPermissionPage extends StatelessWidget { @override Widget build(final BuildContext context) { return ColoredBox( - color: VidarColors.primaryDarkSpaceCadet, + color: Settings.colorSet.primary, child: Center( child: SizedBox( width: MediaQuery.of(context).size.width * 0.8, - child: const Text( + child: Text( "To use Vidar you need to enable SMS permissions. Enable SMS permissions in the app settings then restart the app.", style: TextStyle( - color: Colors.white, + color: Settings.colorSet.text, fontSize: 20, decoration: TextDecoration.none, ), diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 058de09..9ab808a 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -1,14 +1,15 @@ import "package:flutter/material.dart"; -import "package:logging/logging.dart"; import "package:permission_handler/permission_handler.dart"; -import "package:vidar/configuration.dart"; import "package:vidar/pages/contact_list.dart"; +import "package:vidar/utils/colors.dart"; import "package:vidar/utils/common_object.dart"; import "package:vidar/utils/log.dart"; +import "package:vidar/utils/navigation.dart"; import "package:vidar/utils/settings.dart"; import "package:vidar/utils/storage.dart"; import "package:vidar/widgets/boolean_setting.dart"; import "package:vidar/widgets/buttons.dart"; +import "package:vidar/widgets/color_set_select.dart"; class SettingsPage extends StatefulWidget { const SettingsPage({super.key}); @@ -20,16 +21,30 @@ class SettingsPage extends StatefulWidget { class _SettingsPageState extends State { _SettingsPageState(); - BooleanSetting allowUnencryptedMessages = BooleanSetting( + final BooleanSetting allowUnencryptedMessages = BooleanSetting( setting: Settings.allowUnencryptedMessages, settingText: "Send unencrypted messages when contact has no key", ); - BooleanSetting keepLogs = BooleanSetting( + final BooleanSetting keepLogs = BooleanSetting( setting: Settings.keepLogs, settingText: "Keep Logs", ); + final BooleanSetting showEncryptionKeyInEditContact = BooleanSetting( + setting: Settings.showEncryptionKeyInEditContact, + settingText: "Show encryption key when editing contact", + ); + + final BooleanSetting showMessageBarHints = BooleanSetting( + setting: Settings.showMessageBarHints, + settingText: "Show message bar hints", + ); + + final ColorSetSelect colorSetSelect = ColorSetSelect( + selectedSet: Settings.colorSet.name, + ); + @override void initState() { super.initState(); @@ -38,11 +53,15 @@ class _SettingsPageState extends State { Future _save() async { Settings.allowUnencryptedMessages = allowUnencryptedMessages.setting; Settings.keepLogs = keepLogs.setting; + Settings.showEncryptionKeyInEditContact = + showEncryptionKeyInEditContact.setting; if (Settings.keepLogs) { final PermissionStatus manageExternalStorageStatus = await Permission .manageExternalStorage .request(); - if (manageExternalStorageStatus.isDenied) { + if (manageExternalStorageStatus.isGranted) { + createLogger(); + } else { if (mounted) { showDialog( context: context, @@ -54,13 +73,7 @@ class _SettingsPageState extends State { actions: [ TextButton( onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (final BuildContext context) => - const ContactListPage(), - ), - ); + clearNavigatorAndPush(context, const ContactListPage()); }, child: const Text("Continue"), ), @@ -69,12 +82,6 @@ class _SettingsPageState extends State { ); } Settings.keepLogs = false; - return; - } else { - CommonObject.logger = Logger(LoggingConfiguration.loggerName); - CommonObject.logger!.onRecord.listen((final LogRecord log) { - createLogger(); - }); } } else { if (CommonObject.logger != null) { @@ -83,38 +90,37 @@ class _SettingsPageState extends State { } CommonObject.logs = []; } + Settings.showMessageBarHints = showMessageBarHints.setting; + Settings.colorSet = getColorSetFromName(colorSetSelect.selectedSet); if (mounted) { saveSettings(CommonObject.settings, context: context); - Navigator.push( - context, - MaterialPageRoute( - builder: (final BuildContext context) => const ContactListPage(), - ), - ); + clearNavigatorAndPush(context, const ContactListPage()); } } void _discard() { - Navigator.push( - context, - MaterialPageRoute( - builder: (final BuildContext context) => const ContactListPage(), - ), - ); + clearNavigatorAndPush(context, const ContactListPage()); } @override Widget build(final BuildContext context) { return ColoredBox( - color: VidarColors.primaryDarkSpaceCadet, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + color: Settings.colorSet.primary, + child: ListView( children: [ Column( - spacing: 60, + spacing: 20, children: [ - Column(children: [allowUnencryptedMessages, keepLogs]), + Column( + children: [ + allowUnencryptedMessages, + keepLogs, + showEncryptionKeyInEditContact, + showMessageBarHints, + colorSetSelect, + ], + ), Container( margin: const EdgeInsets.only(top: 60), child: Row( @@ -122,15 +128,16 @@ class _SettingsPageState extends State { children: [ BasicButton( buttonText: "Discard", - textColor: Colors.white, - buttonColor: VidarColors.secondaryMetallicViolet, + textColor: Settings.colorSet.text, + buttonColor: Settings.colorSet.secondary, onPressed: _discard, ), BasicButton( buttonText: "Save", - textColor: Colors.white, - buttonColor: VidarColors.tertiaryGold, + textColor: Settings.colorSet.text, + buttonColor: Settings.colorSet.tertiary, onPressed: _save, + fontWeight: FontWeight.bold, ), ], ), @@ -138,47 +145,64 @@ class _SettingsPageState extends State { ], ), Padding( - padding: const EdgeInsets.only(bottom: 150), - child: BasicButton( - buttonText: "Wipe Keys", - textColor: Colors.white, - buttonColor: VidarColors.extraFireBrick, - width: 200, - onPressed: () { - showDialog( - context: context, - builder: (final BuildContext context) { - return AlertDialog( - title: const Text("Wipe all keys"), - content: const Text( - "Are you sure you want to wipe all keys? This is a permanent action which can not be undone.", - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("Cancel"), - ), - TextButton( - onPressed: () { - CommonObject.contactList.wipeKeys(); - wipeSecureStorage(); - Navigator.push( - context, - MaterialPageRoute( - builder: (final BuildContext context) => - const ContactListPage(), - ), - ); - }, - child: const Text("Wipe Keys"), - ), - ], + padding: const EdgeInsets.only(top: 100), + child: Column( + spacing: 50, + children: [ + Visibility( + visible: Settings.keepLogs, + child: BasicButton( + buttonText: "Export Logs", + textColor: Settings.colorSet.text, + buttonColor: Settings.colorSet.exportLogsButton, + onPressed: () => exportLogs(context: context), + width: 200, + ), + ), + BasicButton( + buttonText: "Wipe Keys", + textColor: Settings.colorSet.wipeKeyButtonText, + buttonColor: Settings.colorSet.wipeKeyButton, + width: 200, + onPressed: () { + showDialog( + context: context, + builder: (final BuildContext context) { + return AlertDialog( + title: const Text("Wipe all keys"), + content: const Text( + "Are you sure you want to wipe all keys? This is a permanent action which can not be undone.", + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + TextButton( + onPressed: () { + if (Settings.keepLogs) { + CommonObject.logger!.info( + "Wiping all keys...", + ); + } + CommonObject.contactList.wipeKeys(); + wipeSecureStorage(); + clearNavigatorAndPush( + context, + const ContactListPage(), + ); + }, + child: const Text("Wipe Keys"), + ), + ], + ); + }, ); }, - ); - }, + ), + ], ), ), ], diff --git a/lib/utils/colors.dart b/lib/utils/colors.dart new file mode 100644 index 0000000..aad3735 --- /dev/null +++ b/lib/utils/colors.dart @@ -0,0 +1,110 @@ +import "package:flutter/material.dart"; + +class ColorSet { + ColorSet({ + required this.name, + required this.primary, + required this.secondary, + required this.tertiary, + required this.text, + final Color? pWipeKeyButton, + final Color? pInactiveTrack, + final Color? pDropdownFocus, + final Color? pSendButton, + final Color? pFloatingActionButton, + final Color? pWipeKeyButtonText, + final Color? pMessageBarHintText, + final Color? pExportLogsButton, + }) { + wipeKeyButton = pWipeKeyButton ?? secondary; + inactiveTrack = pInactiveTrack ?? primary; + dropdownFocus = pDropdownFocus ?? tertiary; + sendButton = pSendButton ?? primary; + floatingActionButton = pFloatingActionButton ?? tertiary; + wipeKeyButtonText = pWipeKeyButtonText ?? text; + messageBarHintText = pMessageBarHintText ?? text; + exportLogsButton = pExportLogsButton ?? tertiary; + } + final String name; + final Color primary; + final Color secondary; + final Color tertiary; + final Color text; + + late final Color wipeKeyButton; + late final Color inactiveTrack; + late final Color dropdownFocus; + late final Color sendButton; + late final Color floatingActionButton; + late final Color wipeKeyButtonText; + late final Color messageBarHintText; + late final Color exportLogsButton; +} + +final List availableColorSets = [ + vidarColorSet.name, + playaColorSet.name, + monochromeColorSet.name, + bubblyColorSet.name, +]; + +final ColorSet vidarColorSet = ColorSet( + name: "Default", + primary: const Color.fromARGB(255, 26, 28, 40), + secondary: const Color.fromARGB(255, 53, 22, 100), + tertiary: const Color.fromARGB(255, 177, 140, 25), + text: Colors.white, + pWipeKeyButton: const Color.fromARGB(255, 178, 34, 34), + pFloatingActionButton: const Color.fromARGB(255, 39, 8, 86), + pMessageBarHintText: const Color.fromARGB(255, 172, 116, 255), + pSendButton: const Color.fromARGB(255, 172, 116, 255), +); + +final ColorSet playaColorSet = ColorSet( + name: "Playa", + primary: const Color.fromARGB(255, 255, 250, 141), + secondary: const Color.fromARGB(255, 168, 241, 255), + tertiary: const Color.fromARGB(255, 78, 215, 241), + text: const Color.fromARGB(255, 28, 60, 103), + pWipeKeyButton: const Color.fromARGB(255, 255, 125, 41), + pInactiveTrack: const Color.fromARGB(255, 250, 218, 122), +); + +final ColorSet monochromeColorSet = ColorSet( + name: "Monochrome", + primary: const Color.fromARGB(255, 30, 30, 30), + secondary: Colors.black, + tertiary: const Color.fromARGB(255, 83, 83, 83), + text: Colors.white, + pWipeKeyButton: const Color.fromARGB(255, 200, 200, 200), + pInactiveTrack: const Color.fromARGB(255, 41, 41, 41), + pSendButton: const Color.fromARGB(255, 200, 200, 200), + pWipeKeyButtonText: Colors.black, +); + +final ColorSet bubblyColorSet = ColorSet( + name: "Bubbly", + primary: const Color.fromARGB(255, 8, 75, 131), + secondary: const Color.fromARGB(255, 255, 102, 179), + tertiary: const Color.fromARGB(255, 2, 45, 64), + text: const Color.fromARGB(255, 240, 246, 246), + pWipeKeyButton: const Color.fromARGB(255, 255, 40, 183), + pInactiveTrack: const Color.fromARGB(255, 66, 191, 221), + pSendButton: const Color.fromARGB(255, 8, 75, 131), + pDropdownFocus: const Color.fromARGB(255, 165, 40, 255), + pMessageBarHintText: const Color.fromARGB(255, 66, 191, 221), + pExportLogsButton: const Color.fromARGB(255, 165, 40, 255), +); + +/// If color set is not found then it returns default +ColorSet getColorSetFromName(final String colorSetName) { + if (colorSetName == playaColorSet.name) { + return playaColorSet; + } else if (colorSetName == monochromeColorSet.name) { + return monochromeColorSet; + } else if (colorSetName == bubblyColorSet.name) { + return bubblyColorSet; + } else { + return vidarColorSet; + } +} diff --git a/lib/utils/encryption.dart b/lib/utils/encryption.dart index 6bef012..c9738c0 100644 --- a/lib/utils/encryption.dart +++ b/lib/utils/encryption.dart @@ -50,9 +50,9 @@ Future encryptMessage(final String message, final String key) async { return CryptographicConfiguration.encryptionPrefix + base64.encode(fullEncrypted); - } on Exception catch (error, stackTrace) { + } catch (error, stackTrace) { if (Settings.keepLogs) { - CommonObject.logger!.warning( + CommonObject.logger!.finest( "Failed to encrypt message", error, stackTrace, @@ -119,9 +119,14 @@ Future decryptMessage( ); final String decryptedMessage = utf8.decode(decryptedBytes); return decryptedMessage; - } on Exception catch (error, stackTrace) { + } on SecretBoxAuthenticationError catch (error) { if (Settings.keepLogs) { - CommonObject.logger!.warning( + CommonObject.logger!.warning("Failed to decrypt message", error); + } + return "${MiscellaneousConfiguration.errorPrefix}$ENCRYPTION_ERROR_DECRYPTION_FAILED"; + } catch (error, stackTrace) { + if (Settings.keepLogs) { + CommonObject.logger!.finer( "Failed to decrypt message", error, stackTrace, diff --git a/lib/utils/log.dart b/lib/utils/log.dart index c3d8eb0..f5002b5 100644 --- a/lib/utils/log.dart +++ b/lib/utils/log.dart @@ -1,25 +1,45 @@ import "dart:io"; -import "package:flutter/material.dart" - show BuildContext, debugPrint, showDialog; +import "package:device_info_plus/device_info_plus.dart" show DeviceInfoPlugin; +import "package:flutter/material.dart"; import "package:logging/logging.dart"; +import "package:package_info_plus/package_info_plus.dart" show PackageInfo; import "package:permission_handler/permission_handler.dart"; import "package:vidar/configuration.dart"; +import "package:vidar/pages/contact_list.dart"; import "package:vidar/utils/common_object.dart"; +import "package:vidar/utils/navigation.dart"; import "package:vidar/utils/settings.dart"; import "package:vidar/widgets/error_popup.dart"; -void createLogger() { +Future createLogger() async { CommonObject.logger = Logger(LoggingConfiguration.loggerName); + if (hierarchicalLoggingEnabled) { + CommonObject.logger!.level = Level.ALL; + } CommonObject.logger!.onRecord.listen((final LogRecord log) { - if (log.level != Level.INFO && log.level != Level.CONFIG) { - debugPrint(LoggingConfiguration.verboseLogMessage(log)); - CommonObject.logs.add(LoggingConfiguration.verboseLogMessage(log)); - } else { - debugPrint(LoggingConfiguration.conciseLogMessage(log)); - CommonObject.logs.add(LoggingConfiguration.conciseLogMessage(log)); + late final String logMessage; + switch (log.level) { + case Level.CONFIG: + logMessage = LoggingConfiguration.minimalLogMessage(log); + case Level.INFO: + logMessage = LoggingConfiguration.conciseLogMessage(log); + case Level.WARNING: + logMessage = LoggingConfiguration.normalLogMessage(log); + default: + logMessage = LoggingConfiguration.verboseLogMessage(log); } + + debugPrint(logMessage); + CommonObject.logs.add(logMessage); }); + + CommonObject.logger!.config( + LoggingConfiguration.initLog( + packageInfo: await PackageInfo.fromPlatform(), + deviceInfo: await DeviceInfoPlugin().androidInfo, + ), + ); } Future exportLogs({final BuildContext? context}) async { @@ -56,7 +76,7 @@ Future exportLogs({final BuildContext? context}) async { } if (Settings.keepLogs) { - CommonObject.logger!.warning("Failed to export logs", error, stackTrace); + CommonObject.logger!.finest("Failed to export logs", error, stackTrace); } return; @@ -65,10 +85,16 @@ Future exportLogs({final BuildContext? context}) async { if (context != null && context.mounted) { showDialog( context: context, - builder: (final BuildContext context) => ErrorPopup( - title: "Logs exported", - body: 'Logs have been exported to "${file.path}"', - enableReturn: false, + builder: (final BuildContext context) => AlertDialog( + title: const Text("Logs exported"), + content: Text('Logs have been exported to "${file.path}"'), + actions: [ + TextButton( + onPressed: () => + clearNavigatorAndPush(context, const ContactListPage()), + child: const Text("Dismiss"), + ), + ], ), ); } @@ -77,3 +103,5 @@ Future exportLogs({final BuildContext? context}) async { CommonObject.logger!.info('Logs have been exported to "${file.path}"'); } } + +const Level initLog = const Level("INIT", 601); diff --git a/lib/utils/settings.dart b/lib/utils/settings.dart index ede2ddd..d6d0030 100644 --- a/lib/utils/settings.dart +++ b/lib/utils/settings.dart @@ -1,3 +1,4 @@ +import "package:vidar/utils/colors.dart"; import "package:vidar/utils/common_object.dart"; /// Static class for storing the active user settings of the program. @@ -7,11 +8,20 @@ class Settings { static bool keepLogs = false; + static bool showEncryptionKeyInEditContact = false; + + static bool showMessageBarHints = true; + + static ColorSet colorSet = vidarColorSet; + /// Get map of the state of all instance variable of Settings. Map toMap() { return { "allowUnencryptedMessages": allowUnencryptedMessages, "keepLogs": keepLogs, + "showEncryptionKeyInEditContact": showEncryptionKeyInEditContact, + "showMessageBarHints": showMessageBarHints, + "colorSet": colorSet.name, }; } @@ -19,6 +29,12 @@ class Settings { /// If setting is not found then it goes to the default setting void fromMap(final Map map) { keepLogs = map["keepLogs"] as bool? ?? keepLogs; + showEncryptionKeyInEditContact = + map["showEncryptionKeyInEditContact"] as bool? ?? + showEncryptionKeyInEditContact; + showMessageBarHints = + map["showMessageBarHints"] as bool? ?? showMessageBarHints; + colorSet = getColorSetFromName(map["colorSet"] as String? ?? "default"); final bool? newAllowUnencryptedMessages = map["allowUnencryptedMessages"]! as bool?; diff --git a/lib/utils/sms.dart b/lib/utils/sms.dart index 894286c..931680e 100644 --- a/lib/utils/sms.dart +++ b/lib/utils/sms.dart @@ -104,10 +104,10 @@ SmsMessage? _queryMapToSms(final Map smsMap) { ); final int? type = int.tryParse(smsMap[SmsConstants.COLUMN_NAME_TYPE]!); final String phoneNumber = smsMap[SmsConstants.COLUMN_NAME_ADDRESS]!; - final DateTime date = DateTime.fromMicrosecondsSinceEpoch( + final DateTime date = DateTime.fromMillisecondsSinceEpoch( int.parse(smsMap[SmsConstants.COLUMN_NAME_DATE]!), ); - final DateTime dateSent = DateTime.fromMicrosecondsSinceEpoch( + final DateTime dateSent = DateTime.fromMillisecondsSinceEpoch( int.parse(smsMap[SmsConstants.COLUMN_NAME_DATE_SENT]!), ); final bool seen = int.parse(smsMap[SmsConstants.COLUMN_NAME_SEEN]!) != 0; @@ -170,13 +170,9 @@ Future> querySms({final String? phoneNumber}) async { } } return smsMessages; - } on PlatformException catch (e) { + } on PlatformException catch (error, stackTrace) { if (Settings.keepLogs) { - CommonObject.logger!.warning( - "Sms query failed", - e.message, - e.stacktrace == null ? null : StackTrace.fromString(e.stacktrace!), - ); + CommonObject.logger!.finer("Sms query failed", error, stackTrace); } return [null]; } diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 7d74f30..525a5ba 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -80,7 +80,7 @@ Future loadData( } if (Settings.keepLogs) { - createLogger(); + await createLogger(); } } on Exception catch (error, stackTrace) { if (context != null && context.mounted) { @@ -110,7 +110,14 @@ Future saveSettings( sharedPreferences ?? await SharedPreferences.getInstance(); await prefs.setString("settings", jsonEncode(settings.toMap())); if (Settings.keepLogs) { - CommonObject.logger!.info("Settings: ${jsonEncode(settings.toMap())}"); + // Showing Settings.keepLogs is redundant but it's there for consistency + CommonObject.logger!.config(""" + ======== SETTINGS ======== + Allow Unencrypted Messages: ${Settings.allowUnencryptedMessages} + Keep Logs: ${Settings.keepLogs} + Color set: ${Settings.colorSet.name} + Show Message Bar Hints: ${Settings.showMessageBarHints} + """); } } on Exception catch (error, stackTrace) { if (context != null && context.mounted) { diff --git a/lib/widgets/boolean_setting.dart b/lib/widgets/boolean_setting.dart index 3e4f971..2af5491 100644 --- a/lib/widgets/boolean_setting.dart +++ b/lib/widgets/boolean_setting.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:vidar/configuration.dart"; +import "package:vidar/utils/settings.dart"; // ignore: must_be_immutable class BooleanSetting extends StatefulWidget { @@ -31,7 +32,7 @@ class _BooleanSettingState extends State { Widget build(final BuildContext context) { return Container( margin: const EdgeInsets.only(top: 40), - color: VidarColors.primaryDarkSpaceCadet, + color: Settings.colorSet.primary, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -42,14 +43,14 @@ class _BooleanSettingState extends State { child: Material( color: Colors.transparent, child: Switch( - activeColor: VidarColors.tertiaryGold, - inactiveThumbColor: VidarColors.secondaryMetallicViolet, - inactiveTrackColor: VidarColors.extraMidnightPurple, + activeColor: Settings.colorSet.tertiary, + inactiveThumbColor: Settings.colorSet.secondary, + inactiveTrackColor: Settings.colorSet.inactiveTrack, trackOutlineColor: WidgetStateProperty.resolveWith( (final Set states) => states.contains(WidgetState.selected) ? null - : VidarColors.secondaryMetallicViolet, + : Settings.colorSet.secondary, ), value: widget.setting, onChanged: (final bool value) { @@ -65,8 +66,8 @@ class _BooleanSettingState extends State { width: MediaQuery.of(context).size.width * 0.6, child: Text( settingText, - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: Settings.colorSet.text, fontSize: SizeConfiguration.settingInfoText, decoration: TextDecoration.none, ), diff --git a/lib/widgets/buttons.dart b/lib/widgets/buttons.dart index e49316b..83dd477 100644 --- a/lib/widgets/buttons.dart +++ b/lib/widgets/buttons.dart @@ -8,6 +8,7 @@ class BasicButton extends StatelessWidget { required this.onPressed, this.width = 100, this.height = 50, + this.fontWeight = FontWeight.normal, super.key, }); final Color textColor; @@ -16,6 +17,7 @@ class BasicButton extends StatelessWidget { final VoidCallback onPressed; final double width; final double height; + final FontWeight fontWeight; @override Widget build(final BuildContext context) { @@ -31,7 +33,11 @@ class BasicButton extends StatelessWidget { ), child: Text( buttonText, - style: TextStyle(fontSize: 24, color: textColor), + style: TextStyle( + fontSize: 24, + color: textColor, + fontWeight: fontWeight, + ), ), ), ); diff --git a/lib/widgets/color_set_select.dart b/lib/widgets/color_set_select.dart new file mode 100644 index 0000000..1701721 --- /dev/null +++ b/lib/widgets/color_set_select.dart @@ -0,0 +1,73 @@ +import "package:flutter/material.dart"; +import "package:vidar/utils/colors.dart"; +import "package:vidar/utils/settings.dart"; + +// ignore: must_be_immutable +class ColorSetSelect extends StatefulWidget { + ColorSetSelect({required this.selectedSet, super.key}); + + String selectedSet; + + @override + _ColorSetSelectState createState() => _ColorSetSelectState(); +} + +class _ColorSetSelectState extends State { + _ColorSetSelectState(); + + late final String settingText; + + @override + Widget build(final BuildContext context) { + return Container( + margin: const EdgeInsets.only(top: 40), + color: Settings.colorSet.primary, + child: Material( + color: Colors.transparent, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + color: Settings.colorSet.secondary, + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + borderRadius: BorderRadius.circular(10.0), + padding: EdgeInsets.only( + left: MediaQuery.of(context).size.width * 0.1, + right: MediaQuery.of(context).size.width * 0.05, + ), + dropdownColor: Settings.colorSet.secondary, + focusColor: Settings.colorSet.dropdownFocus, + icon: Icon( + Icons.arrow_circle_down_sharp, + color: Settings.colorSet.text, + ), + + style: TextStyle(color: Settings.colorSet.text), + value: widget.selectedSet, + items: availableColorSets + .map>( + (final String value) => DropdownMenuItem( + value: value, + child: Text(value), + ), + ) + .toList(), + onChanged: (final String? value) { + if (value != null) { + setState(() { + widget.selectedSet = value; + }); + } + }, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/contact_badge.dart b/lib/widgets/contact_badge.dart index 550cbff..5b59a00 100644 --- a/lib/widgets/contact_badge.dart +++ b/lib/widgets/contact_badge.dart @@ -21,7 +21,7 @@ class ContactBadge extends StatelessWidget { padding: const EdgeInsets.only(top: 10, bottom: 10), margin: const EdgeInsets.only(top: 5, bottom: 5, left: 10, right: 10), decoration: BoxDecoration( - color: VidarColors.primaryDarkSpaceCadet, + color: Settings.colorSet.primary, borderRadius: BorderRadius.circular(10), ), @@ -29,17 +29,17 @@ class ContactBadge extends StatelessWidget { children: [ Text( contact.name, - style: const TextStyle( + style: TextStyle( fontSize: 32, - color: Colors.white, + color: Settings.colorSet.text, decoration: TextDecoration.none, ), ), Text( contact.phoneNumber, - style: const TextStyle( + style: TextStyle( fontSize: 12, - color: Colors.white, + color: Settings.colorSet.text, decoration: TextDecoration.none, ), ), diff --git a/lib/widgets/conversation_widget.dart b/lib/widgets/conversation_widget.dart index a7eb836..899e78f 100644 --- a/lib/widgets/conversation_widget.dart +++ b/lib/widgets/conversation_widget.dart @@ -63,16 +63,16 @@ class _ConversationWidgetState extends State { ); } return ColoredBox( - color: VidarColors.primaryDarkSpaceCadet, + color: Settings.colorSet.primary, child: Center( child: SizedBox( width: MediaQuery.of(context).size.width * 0.6, - child: const Center( + child: Center( child: Text( "SMS query failed, please ensure the phone number is correct.", style: TextStyle( fontSize: 20, - color: Colors.white, + color: Settings.colorSet.text, ), ), ), @@ -125,7 +125,7 @@ class _ConversationWidgetState extends State { if (LoggingConfiguration.extraVerboseLogs && Settings.keepLogs) { CommonObject.logger!.info( - "Snapshot has not data for contact ${contact.uuid}", + "Snapshot has no data for contact ${contact.uuid}", ); } } @@ -135,7 +135,7 @@ class _ConversationWidgetState extends State { ); } return ColoredBox( - color: VidarColors.primaryDarkSpaceCadet, + color: Settings.colorSet.primary, child: ListView( reverse: true, children: decryptedSpeechBubbles, diff --git a/lib/widgets/error_popup.dart b/lib/widgets/error_popup.dart index 6c54808..dc5938c 100644 --- a/lib/widgets/error_popup.dart +++ b/lib/widgets/error_popup.dart @@ -1,6 +1,7 @@ import "package:flutter/material.dart"; import "package:vidar/pages/contact_list.dart"; import "package:vidar/utils/log.dart"; +import "package:vidar/utils/navigation.dart"; import "package:vidar/utils/settings.dart"; /// Alert dialog template for errors @@ -36,12 +37,7 @@ class _ErrorPopupState extends State { final List list = [ TextButton( onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (final BuildContext context) => const ContactListPage(), - ), - ); + clearNavigatorAndPush(context, const ContactListPage()); }, child: const Text("Home"), ), @@ -51,15 +47,9 @@ class _ErrorPopupState extends State { TextButton( onPressed: () { exportLogs(); - Navigator.push( - context, - MaterialPageRoute( - builder: (final BuildContext context) => - const ContactListPage(), - ), - ); + clearNavigatorAndPush(context, const ContactListPage()); }, - child: const Text("Save logs"), + child: const Text("Export logs"), ), ); } diff --git a/lib/widgets/loading_screen.dart b/lib/widgets/loading_screen.dart index 9da8fbd..6b85e40 100644 --- a/lib/widgets/loading_screen.dart +++ b/lib/widgets/loading_screen.dart @@ -2,6 +2,7 @@ import "package:flutter/material.dart"; import "package:vidar/configuration.dart"; +import "package:vidar/utils/settings.dart"; import "package:vidar/widgets/loading_text.dart"; class ChatLoadingScreen extends LoadingScreen { @@ -25,7 +26,7 @@ class LoadingScreen extends StatelessWidget { @override Widget build(final BuildContext context) { return ColoredBox( - color: VidarColors.primaryDarkSpaceCadet, + color: Settings.colorSet.primary, child: Column( spacing: 30, mainAxisAlignment: MainAxisAlignment.center, @@ -33,13 +34,13 @@ class LoadingScreen extends StatelessWidget { Transform.scale( scale: 2, child: CircularProgressIndicator( - color: VidarColors.secondaryMetallicViolet, + color: Settings.colorSet.secondary, ), ), DecryptingText( targets: loadingMessages, style: TextStyle( - color: VidarColors.secondaryMetallicViolet, + color: Settings.colorSet.secondary, fontSize: SizeConfiguration.loadingFontSize, ), ), diff --git a/lib/widgets/message_bar.dart b/lib/widgets/message_bar.dart index f8c8627..af8b6ac 100644 --- a/lib/widgets/message_bar.dart +++ b/lib/widgets/message_bar.dart @@ -1,3 +1,5 @@ +import "dart:math"; + import "package:flutter/material.dart"; import "package:vidar/configuration.dart"; import "package:vidar/utils/common_object.dart"; @@ -41,10 +43,10 @@ class _MessageBarState extends State { final String text, ) { return Container( - color: VidarColors.tertiaryGold, + color: Settings.colorSet.tertiary, padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom > 50 - ? MediaQuery.of(context).viewInsets.bottom + ? MediaQuery.of(context).viewInsets.bottom + 10 : 50, left: 20, right: 20, @@ -57,8 +59,8 @@ class _MessageBarState extends State { width: MediaQuery.of(context).size.width * 0.75, child: Text( text, - style: const TextStyle( - color: VidarColors.secondaryMetallicViolet, + style: TextStyle( + color: Settings.colorSet.secondary, fontWeight: FontWeight.bold, fontSize: 16, ), @@ -69,10 +71,7 @@ class _MessageBarState extends State { error = false; errorNotifier.notifyListeners(); }, - icon: const Icon( - Icons.sms, - color: VidarColors.secondaryMetallicViolet, - ), + icon: Icon(Icons.sms, color: Settings.colorSet.secondary), ), ], ), @@ -114,86 +113,102 @@ class _MessageBarState extends State { } } else { return Container( - color: VidarColors.tertiaryGold, + color: Settings.colorSet.secondary, padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom > 50 - ? MediaQuery.of(context).viewInsets.bottom + ? MediaQuery.of(context).viewInsets.bottom + 10 : 50, ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - decoration: BoxDecoration( - color: VidarColors.secondaryMetallicViolet, - borderRadius: BorderRadius.circular(4), - ), - padding: const EdgeInsets.only(left: 10), - width: - MediaQuery.sizeOf(context).width - - SizeConfiguration.sendMessageIconSize * 2.5, - child: TextField( - controller: controller, - style: const TextStyle(color: Colors.white), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide.none, + child: Padding( + padding: const EdgeInsets.only(top: 5.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + decoration: BoxDecoration( + color: Settings.colorSet.primary, + borderRadius: BorderRadius.circular(4.0), + ), + padding: const EdgeInsets.only(left: 10.0), + width: + MediaQuery.sizeOf(context).width - + SizeConfiguration.sendMessageIconSize * 2.5, + child: TextField( + controller: controller, + style: TextStyle(color: Settings.colorSet.text), + decoration: InputDecoration( + hintText: () { + if (Settings.showMessageBarHints) { + return MiscellaneousConfiguration + .messageHints[Random().nextInt( + MiscellaneousConfiguration.messageHints.length, + )]; + } else { + return null; + } + }(), + hintStyle: TextStyle( + color: Settings.colorSet.messageBarHintText, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + borderSide: BorderSide.none, + ), ), + onChanged: (final String value) => message = value, ), - onChanged: (final String value) => message = value, ), - ), - SizedBox( - width: 50, - height: 60, - child: Center( - child: IconButton( - onPressed: () async { - final String encryptedMessage = await encryptMessage( - message, - contact.encryptionKey, - ); - if (encryptedMessage.startsWith( - MiscellaneousConfiguration.errorPrefix, - )) { - errorMessage = encryptedMessage.replaceFirst( - MiscellaneousConfiguration.errorPrefix, - "", + SizedBox( + width: 50.0, + height: 60.0, + child: Center( + child: IconButton( + onPressed: () async { + final String encryptedMessage = await encryptMessage( + message, + contact.encryptionKey, ); - error = true; - errorNotifier.notifyListeners(); - } else { - sendSms(encryptedMessage, contact.phoneNumber); - controller.text = - ""; // Clear only after successful send - if (CommonObject.currentConversation != null) { - int delay = - (encryptedMessage.length ~/ 65) * - TimeConfiguration.smsUpdateDelay; - delay = delay == 0 ? 1 : delay; - Future.delayed(Duration(seconds: delay)).then( - (_) { + if (encryptedMessage.startsWith( + MiscellaneousConfiguration.errorPrefix, + )) { + errorMessage = encryptedMessage.replaceFirst( + MiscellaneousConfiguration.errorPrefix, + "", + ); + error = true; + errorNotifier.notifyListeners(); + } else { + sendSms(encryptedMessage, contact.phoneNumber); + controller.text = + ""; // Clear only after successful send + if (CommonObject.currentConversation != null) { + int delay = + (encryptedMessage.length ~/ 65) * + TimeConfiguration.smsUpdateDelay; + delay = delay == 0 ? 1 : delay; + Future.delayed( + Duration(seconds: delay), + ).then((_) { CommonObject.currentConversation! .notifyListeners(); - }, - ); - } else if (Settings.keepLogs) { - CommonObject.logger!.info( - "Current conversation is null, can not notifyListeners", - ); + }); + } else if (Settings.keepLogs) { + CommonObject.logger!.info( + "Current conversation is null, can not notifyListeners", + ); + } } - } - }, - icon: const Icon( - size: SizeConfiguration.sendMessageIconSize, - Icons.send, - color: VidarColors.secondaryMetallicViolet, + }, + icon: Icon( + size: SizeConfiguration.sendMessageIconSize, + Icons.send, + color: Settings.colorSet.sendButton, + ), ), ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/widgets/speech_bubble.dart b/lib/widgets/speech_bubble.dart index baeb088..123f911 100644 --- a/lib/widgets/speech_bubble.dart +++ b/lib/widgets/speech_bubble.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:vidar/configuration.dart"; +import "package:vidar/utils/settings.dart"; import "package:vidar/utils/sms.dart"; class SpeechBubble extends StatelessWidget { @@ -34,8 +35,8 @@ class SpeechBubble extends StatelessWidget { ), decoration: BoxDecoration( color: isMe - ? VidarColors.secondaryMetallicViolet - : VidarColors.tertiaryGold, + ? Settings.colorSet.secondary + : Settings.colorSet.tertiary, borderRadius: BorderRadius.only( topLeft: const Radius.circular(10), topRight: const Radius.circular(10), @@ -45,21 +46,18 @@ class SpeechBubble extends StatelessWidget { ), child: Text( message.body, - style: const TextStyle(color: Colors.white, fontSize: 12), + style: TextStyle(color: Settings.colorSet.text, fontSize: 12), ), ), ), - // Temporarily disabled until fix - /*Container( - margin: EdgeInsets.only(top: 2, bottom: 2), + Container( + margin: const EdgeInsets.only(top: 2.0, bottom: 2.0), child: Text( (isMe ? "Sent at " : "Received at ") + - (isMe - ? message.dateSent!.toIso8601String().substring(11, 16) - : message.date!.toIso8601String().substring(11, 16)), + message.date!.toIso8601String().substring(11, 16), style: const TextStyle(color: Colors.white, fontSize: 8), ), - ),*/ + ), ], ), ), diff --git a/pubspec.lock b/pubspec.lock index 4dc3762..bcb9b40 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,14 +9,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" characters: dependency: transitive description: @@ -57,14 +49,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.0" - fake_async: + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" + url: "https://pub.dev" + source: hosted + version: "11.5.0" + device_info_plus_platform_interface: dependency: transitive description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "7.0.3" ffi: dependency: transitive description: @@ -142,48 +142,35 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" flutter_web_plugins: dependency: transitive description: flutter source: sdk version: "0.0.0" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" - leak_tracker: + http: dependency: transitive description: - name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "10.0.9" - leak_tracker_flutter_testing: + version: "1.4.0" + http_parser: dependency: transitive description: - name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "3.0.9" - leak_tracker_testing: + version: "4.1.2" + js: dependency: transitive description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "0.6.7" logging: dependency: "direct main" description: @@ -192,14 +179,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" - source: hosted - version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -216,6 +195,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" path: dependency: transitive description: @@ -225,7 +220,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: "direct main" + dependency: transitive description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" @@ -413,22 +408,6 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" string_scanner: dependency: transitive description: @@ -445,14 +424,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - url: "https://pub.dev" - source: hosted - version: "0.7.4" typed_data: dependency: transitive description: @@ -477,14 +448,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 - url: "https://pub.dev" - source: hosted - version: "15.0.0" web: dependency: transitive description: @@ -501,6 +464,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.14.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + url: "https://pub.dev" + source: hosted + version: "2.1.0" xdg_directories: dependency: transitive description: @@ -511,4 +482,4 @@ packages: version: "1.1.0" sdks: dart: ">=3.8.1 <4.0.0" - flutter: ">=3.27.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index fbd1a66..538a565 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,11 +17,8 @@ dependencies: flutter_secure_storage: ^9.2.4 logging: ^1.3.0 uuid: ^4.5.1 - path_provider: ^2.1.5 - -dev_dependencies: - flutter_test: - sdk: flutter + device_info_plus: ^11.5.0 + package_info_plus: ^8.3.0 flutter: uses-material-design: true