diff --git a/assets/images/logo/welcome.png b/assets/images/logo/welcome.png new file mode 100644 index 0000000..6126178 Binary files /dev/null and b/assets/images/logo/welcome.png differ diff --git a/assets/images/logo/welcome.svg b/assets/images/logo/welcome.svg new file mode 100644 index 0000000..c38bd33 --- /dev/null +++ b/assets/images/logo/welcome.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/main.dart b/lib/main.dart index 6992ff1..f081039 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,8 @@ import 'dart:io'; import 'package:drifter/pages/login_screen/login_screen.dart'; import 'package:drifter/pages/main_screen/main_screen_widget.dart'; import 'package:drifter/pages/splash_screen/splash_screen.dart'; +import 'package:drifter/pages/terms_of_service/terms_of_service.dart'; +import 'package:drifter/pages/welcome_screen/welcome_screen.dart'; import 'package:drifter/theme/app_colors.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -27,8 +29,11 @@ class MyApp extends StatelessWidget { ), ), routes: { - '/': (context) => const Splash(), + '/': (context) => const WelcomeScreen() + // Splash() + , '/login': (context) => const LoginScreen(), + '/terms': (context) => const TermsOfServiceScreen(), }, ); } diff --git a/lib/pages/login_screen/login_screen.dart b/lib/pages/login_screen/login_screen.dart index 61342db..b884f8b 100644 --- a/lib/pages/login_screen/login_screen.dart +++ b/lib/pages/login_screen/login_screen.dart @@ -6,6 +6,7 @@ import 'package:drifter/pages/profile_screen/profile_screen.dart'; import 'package:drifter/pages/profile_screen/widgets/keys_option_modal_bottom_sheet.dart'; import 'package:drifter/pages/profile_screen/widgets/message_snack_bar.dart'; import 'package:drifter/pages/profile_screen/widgets/ok_button_widget.dart'; +import 'package:drifter/pages/terms_of_service/button_continue.dart'; import 'package:drifter/theme/app_colors.dart'; import 'package:drifter/utilities/assets.dart'; @@ -13,6 +14,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/svg.dart'; import 'package:nostr_tools/nostr_tools.dart'; +import 'package:sliding_switch/sliding_switch.dart'; +import 'package:toggle_switch/toggle_switch.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @@ -28,142 +31,187 @@ class LoginScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Row( - // mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - Assets.svg.drifterIcon, - height: 30, - width: 30, - alignment: Alignment.centerLeft, - ), - const SizedBox( - width: 125, - ), - const Text( - "Login", - style: TextStyle( - color: AppColors.mainAccent, - ), - // textAlign: TextAlign.center, - ), - ], + appBar: AppBar( + leading: IconButton( + icon: Icon( + Icons.arrow_back, + color: AppColors.topNavIconBack, ), - centerTitle: true, + onPressed: () => Navigator.of(context).pop(), ), - body: ListView( + elevation: 0, + backgroundColor: AppColors.mainBackground, + ), + backgroundColor: AppColors.mainBackground, + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( children: [ const SizedBox( - height: 200, + height: 56, ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - const Text( - 'Enter your private key', - style: TextStyle( - fontSize: 20, - ), - ), - const SizedBox(height: 30), - TextFormField( - decoration: const InputDecoration( - labelText: 'Enter nsec or hex', - border: OutlineInputBorder(), - ), - maxLength: 64, - controller: keyController, - key: formKey, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your private key'; - } - try { - bool isValidHexKey = Nostr.instance.keysService - .isValidPrivateKey(value); - bool isValidNSec = value.trim().startsWith('nsec') && - Nostr.instance.keysService.isValidPrivateKey(Nostr - .instance.keysService - .decodeNsecKeyToPrivateKey(value)); - if (!(isValidHexKey || isValidNSec)) { - return 'Your private key is not valid.'; - } - } on ChecksumVerificationException catch (e) { - return e.message; - } catch (e) { - return 'Error: $e'; - } - return null; - }), - ], - ), - ), - SizedBox(height: 20), - Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(AppColors.background)), - child: const Text( - 'Login', + ToggleSwitch( + minWidth: double.infinity, + minHeight: 56, + totalSwitches: 2, + labels: ['Login', 'View only'], + activeBgColor: [AppColors.ToggleSwitchActiveBg], + activeFgColor: AppColors.ToggleSwitchTextActive, + inactiveBgColor: AppColors.ToggleSwitchBg, + inactiveFgColor: AppColors.ToggleSwitchTextInactive, + activeBorders: [ + Border.all( + color: AppColors.ToggleSwitchBg, + width: 4, ), - onPressed: () { - if (formKey.currentState!.validate()) { - String privateKeyHex = keyController.text.trim(); - String publicKeyHex; - String nsecKey; - String npubKey; - - // if (privateKeyHex.startsWith('nsec')) { - // nsecKey = privateKeyHex; - // final decoded = Nostr.instance.keysService - // .decodeNsecKeyToPrivateKey(privateKeyHex); - // privateKeyHex = decoded; - // publicKeyHex = Nostr.instance.keysService - // .derivePublicKey(privateKey: 'privateKeyHex'); - // npubKey = Nostr.instance.keysService - // .encodePublicKeyToNpub(publicKeyHex); - // } else { - // publicKeyHex = Nostr.instance.keysService - // .derivePublicKey(privateKey: 'privateKeyHex'); - // nsecKey = Nostr.instance.keysService - // .encodePrivateKeyToNsec(privateKeyHex); - // npubKey = Nostr.instance.keysService - // .encodePublicKeyToNpub(publicKeyHex); - // } - - if (privateKeyHex.startsWith('nsec')) { - final decoded = nip19.decode(privateKeyHex); - privateKeyHex = decoded['data']; - publicKeyHex = keyGenerator.getPublicKey(privateKeyHex); - nsecKey = nip19.nsecEncode(privateKeyHex); - npubKey = nip19.npubEncode(publicKeyHex); - } else { - publicKeyHex = keyGenerator.getPublicKey(privateKeyHex); - nsecKey = nip19.nsecEncode(privateKeyHex); - npubKey = nip19.npubEncode(publicKeyHex); - } - try { - ProfileScreenState().addKeyToStorage( - privateKeyHex, publicKeyHex, nsecKey, npubKey); - - keyController.clear(); - ScaffoldMessenger.of(context).showSnackBar( - MessageSnackBar(label: 'Congratulations! Keys Stored!'), - ); - - Navigator.of(context).pop(true); - } catch (e) {} - } else { - formKey.currentState?.setState(() {}); + ], + radiusStyle: true, + cornerRadius: 8, + customTextStyles: [ + TextStyle(fontSize: 14, fontWeight: FontWeight.w600) + ], + onToggle: (index) {}, + ), + SizedBox( + height: 56, + ), + const Text( + 'Login', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600), + ), + const SizedBox(height: 13), + const Text( + 'Paste your password (AKA Private Key)', + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w400), + ), + const SizedBox(height: 24), + TextFormField( + decoration: const InputDecoration( + labelText: 'nsec... / hex...', + border: OutlineInputBorder(), + ), + controller: keyController, + key: formKey, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your private key'; } - }, + try { + bool isValidHexKey = + Nostr.instance.keysService.isValidPrivateKey(value); + bool isValidNSec = value.trim().startsWith('nsec') && + Nostr.instance.keysService.isValidPrivateKey(Nostr + .instance.keysService + .decodeNsecKeyToPrivateKey(value)); + if (!(isValidHexKey || isValidNSec)) { + return 'Your private key is not valid.'; + } + } on ChecksumVerificationException catch (e) { + return e.message; + } catch (e) { + return 'Error: $e'; + } + return null; + }), + const SizedBox(height: 10), + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, ), - ) + height: 168, + child: Note.note, + ), + Expanded(child: SizedBox()), + SizedBox( + width: double.infinity, + height: 56, + child: ElevatedButton( + onPressed: () { + if (formKey.currentState!.validate()) { + String privateKeyHex = keyController.text.trim(); + String publicKeyHex; + String nsecKey; + String npubKey; + + // if (privateKeyHex.startsWith('nsec')) { + // nsecKey = privateKeyHex; + // final decoded = Nostr.instance.keysService + // .decodeNsecKeyToPrivateKey(privateKeyHex); + // privateKeyHex = decoded; + // publicKeyHex = Nostr.instance.keysService + // .derivePublicKey(privateKey: 'privateKeyHex'); + // npubKey = Nostr.instance.keysService + // .encodePublicKeyToNpub(publicKeyHex); + // } else { + // publicKeyHex = Nostr.instance.keysService + // .derivePublicKey(privateKey: 'privateKeyHex'); + // nsecKey = Nostr.instance.keysService + // .encodePrivateKeyToNsec(privateKeyHex); + // npubKey = Nostr.instance.keysService + // .encodePublicKeyToNpub(publicKeyHex); + // } + + if (privateKeyHex.startsWith('nsec')) { + final decoded = nip19.decode(privateKeyHex); + privateKeyHex = decoded['data']; + publicKeyHex = keyGenerator.getPublicKey(privateKeyHex); + nsecKey = nip19.nsecEncode(privateKeyHex); + npubKey = nip19.npubEncode(publicKeyHex); + } else { + publicKeyHex = keyGenerator.getPublicKey(privateKeyHex); + nsecKey = nip19.nsecEncode(privateKeyHex); + npubKey = nip19.npubEncode(publicKeyHex); + } + try { + ProfileScreenState().addKeyToStorage( + privateKeyHex, publicKeyHex, nsecKey, npubKey); + + keyController.clear(); + ScaffoldMessenger.of(context).showSnackBar( + MessageSnackBar( + label: 'Congratulations! Keys Stored!'), + ); + + Navigator.of(context).pop(true); + } catch (e) {} + } else { + formKey.currentState?.setState(() {}); + } + }, + child: Text( + 'Continue', + style: TextStyle(fontSize: 16), + ), + style: ButtonStyle( + shape: MaterialStatePropertyAll(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100))), + backgroundColor: const MaterialStatePropertyAll( + AppColors.buttonPrimaryDefaultBg), + foregroundColor: const MaterialStatePropertyAll( + AppColors.buttonPrimaryDefaultText)), + )), ], - )); + ), + ), + ); } } + +abstract class Note { + static final note = Text.rich( + TextSpan( + style: TextStyle( + height: 1.6, + fontSize: 10, + fontWeight: FontWeight.w400, + ), + children: [ + TextSpan( + text: + "Your private key starts with “nsec” or “hex” and gives your full access to your account. That means, if you log in using your private key, you will be able to make posts and send and receive private messages.\n\n" + "Your public key starts with “npub” and gives your view-only access to account. If you log in using your public key, you won’t be able to make posts or access private messages, but you will be able to view your feed. Go to “View only” tab to log in via your public key.") + ]), + ); +} diff --git a/lib/pages/terms_of_service/button_continue.dart b/lib/pages/terms_of_service/button_continue.dart new file mode 100644 index 0000000..1bda815 --- /dev/null +++ b/lib/pages/terms_of_service/button_continue.dart @@ -0,0 +1,52 @@ +import 'package:drifter/theme/app_colors.dart'; +import 'package:flutter/material.dart'; + +class DisabledElevatedButton extends StatelessWidget { + const DisabledElevatedButton({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: null, + child: Text( + 'Continue', + style: TextStyle(fontSize: 16), + ), + style: ButtonStyle( + shape: MaterialStatePropertyAll( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(100))), + backgroundColor: + const MaterialStatePropertyAll(AppColors.buttonPrimaryDisabledBg), + foregroundColor: const MaterialStatePropertyAll( + AppColors.buttonPrimaryDisabledText)), + ); + } +} + +class ActiveElevatedButton extends StatelessWidget { + const ActiveElevatedButton({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () { + Navigator.pushNamed(context, '/login'); + }, + child: Text( + 'Continue', + style: TextStyle(fontSize: 16), + ), + style: ButtonStyle( + shape: MaterialStatePropertyAll( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(100))), + backgroundColor: + const MaterialStatePropertyAll(AppColors.buttonPrimaryDefaultBg), + foregroundColor: const MaterialStatePropertyAll( + AppColors.buttonPrimaryDefaultText)), + ); + } +} diff --git a/lib/pages/terms_of_service/terms_of_service.dart b/lib/pages/terms_of_service/terms_of_service.dart new file mode 100644 index 0000000..9488246 --- /dev/null +++ b/lib/pages/terms_of_service/terms_of_service.dart @@ -0,0 +1,90 @@ +import 'package:drifter/pages/terms_of_service/button_continue.dart'; +import 'package:drifter/pages/terms_of_service/terms_of_service_text.dart'; +import 'package:drifter/theme/app_colors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter/src/widgets/placeholder.dart'; + +class TermsOfServiceScreen extends StatefulWidget { + const TermsOfServiceScreen({super.key}); + + @override + State createState() => _TermsOfServiceScreenState(); +} + +class _TermsOfServiceScreenState extends State { + bool isChecked = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon( + Icons.arrow_back, + color: AppColors.topNavIconBack, + ), + onPressed: () => Navigator.of(context).pop(), + ), + elevation: 0, + backgroundColor: AppColors.mainBackground, + ), + backgroundColor: AppColors.mainBackground, + body: Padding( + padding: const EdgeInsets.only(top: 10, right: 16, left: 16), + child: Center( + child: Column(children: [ + Text( + 'Terms of service', + style: TextStyle( + color: Color(0xFF302F38), + fontSize: 20, + fontWeight: FontWeight.w600), + ), + Padding( + padding: const EdgeInsets.only(top: 20, bottom: 14), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + ), + height: 568, + child: ListView( + padding: EdgeInsets.all(16), + children: [TermsOfServiceText.termsOfService], + ), + ), + ), + Row( + children: [ + Checkbox( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4)), + checkColor: Colors.white, + activeColor: AppColors.checkboxCheckedBg, + value: isChecked, + onChanged: (bool? value) { + setState(() { + isChecked = value!; + }); + }, + ), + const Text( + 'I agree to the Terms of Service and Privacy Policy', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + ), + ], + ), + SizedBox( + width: double.infinity, + height: 56, + child: isChecked + ? const ActiveElevatedButton() + : const DisabledElevatedButton(), + ), + ]), + ), + ), + ); + } +} diff --git a/lib/pages/terms_of_service/terms_of_service_text.dart b/lib/pages/terms_of_service/terms_of_service_text.dart new file mode 100644 index 0000000..2716ccd --- /dev/null +++ b/lib/pages/terms_of_service/terms_of_service_text.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +abstract class TermsOfServiceText { + static final termsOfService = Text.rich( + TextSpan( + style: TextStyle( + fontSize: 14, + ), + children: [ + TextSpan( + text: 'We do not collect your data\n\n', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + TextSpan( + text: + "Effective as of Jan 20, 2023 for the distributed applications in the Play Store and the F-Droid Catalogue\n\n" + "The Amethyst app for Android does not collect or process any personal information from its users. The app is used to connect to third-party Nostr servers (also called Relays) that may or may not collect personal information and are not covered by this privacy policy. Each third-party relay server comes equipped with its own privacy policy and terms of use that can be viewed through the app or through that server's website. The developers of this open source project or maintainers of the distribution channels (app stores) do not have access to the data located in the user's phone. Accounts are fully maintained by the user. We do not have control over them.\n\n" + "Data from connected accounts is only stored locally on the device when it is required for functionality and performance of Amethyst. This data is strictly confidental and cannot be accessed by other apps (on non-rooted devices). Phone data can be deleted by clearing Amethyst's local storage or uninstalling the app.\n\n" + "You cannot use the Amethyst app for Android to submit Objectionable Content to relays. Objectionable Content includes, but is not limited to: (i) sexually explicit materials; (ii) obscene, defamatory, libelous, slanderous, violent and/or unlawful content or profanity; (iii) content that infringes upon the rights of any third party, including copyright, trademark, privacy, publicity or other personal or proprietary right, or that is deceptive or fraudulent; (iv) content that promotes the use or sale of illegal or regulated substances, tobacco products, ammunition and/or firearms; and (v) illegal content related to gambling.\n\n" + "We reserve the right to modify this Privacy Policy at any time. Any modifications to this Privacy Policy will be effective upon our posting the new terms and/or upon implementation of the new changes on the Service (or as otherwise indicated at the time of posting). In all cases, your continued use of the app after the posting of any modified Privacy Policy indicates your acceptance of the terms of the modified Privacy Policy.\n\n" + "If you have any questions about Amethyst or this privacy policy, you can send a message to amethyst@vitorpamplona.com") + ]), + ); +} diff --git a/lib/pages/welcome_screen/welcome_screen.dart b/lib/pages/welcome_screen/welcome_screen.dart new file mode 100644 index 0000000..86d5c8c --- /dev/null +++ b/lib/pages/welcome_screen/welcome_screen.dart @@ -0,0 +1,73 @@ +import 'package:drifter/theme/app_colors.dart'; +import 'package:flutter/material.dart'; + +class WelcomeScreen extends StatelessWidget { + const WelcomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.mainBackground, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Image(image: AssetImage('assets/images/logo/welcome.png')), + SizedBox( + height: 270, + ), + Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: SizedBox( + height: 56, + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pushNamed(context, '/terms'); + }, + child: Text( + 'Login', + style: TextStyle(fontSize: 16), + ), + style: ButtonStyle( + shape: MaterialStatePropertyAll(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100))), + backgroundColor: const MaterialStatePropertyAll( + AppColors.buttonPrimaryDefaultBg), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox( + height: 56, + width: double.infinity, + child: OutlinedButton( + onPressed: () {}, + child: Text( + 'Create an account', + style: TextStyle(fontSize: 16), + ), + style: ButtonStyle( + side: const MaterialStatePropertyAll(BorderSide( + width: 2, + color: AppColors.buttonOutlinedDefaultBorder)), + shape: MaterialStatePropertyAll(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100))), + backgroundColor: const MaterialStatePropertyAll( + AppColors.mainBackground, + ), + foregroundColor: const MaterialStatePropertyAll( + AppColors.buttonOutlinedDefaultText), + overlayColor: const MaterialStatePropertyAll( + AppColors.buttonOutlinedPressedBg)), + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/theme/app_colors.dart b/lib/theme/app_colors.dart index f1b06ae..02b7df1 100644 --- a/lib/theme/app_colors.dart +++ b/lib/theme/app_colors.dart @@ -1,9 +1,38 @@ import 'package:flutter/material.dart'; abstract class AppColors { - static const background = const Color(0xFF4f46f1); - static const mainAccent = const Color(0xFFFFCC11); - static const mainBackground = const Color(0xFFF2EFFF); + static const background = Color(0xFF4f46f1); + static const mainAccent = Color(0xFFFFCC11); + static const mainBackground = Color(0xFFF2EFFF); + static const buttonPrimaryDefaultBg = Color(0xFF4F46F1); + static const buttonPrimaryDefaultText = Color(0xFFFFFFFF); + static const buttonPrimaryDisabledBg = Color(0x1A18171B); + static const buttonPrimaryDisabledText = Color(0xFFA7A7A8); + static const buttonOutlinedDefaultBorder = Color(0xFF4F46F1); + static const buttonOutlinedDefaultText = Color(0xFF4F46F1); + static const buttonOutlinedPressedBg = Color(0xFFE3E0F9); + +// TopNav + + static const topNavIconPtimary = Color(0xFF787680); + static const topNavText = Color(0xFF000000); + static const topNavIconBack = Color(0xFF4A40EC); + static const topNavIconBg = Color(0xFFE3E0F9); + +// Checkbox + static const checkboxCheckedIcon = Color(0xFFFFFFFF); + static const checkboxCheckedBg = Color(0xFF4A40EC); + static const checkboxEmptyBorder = Color(0xFF302F38); + static const checkboxTextLabel = Color(0xFF302F38); + static const checkboxDisabledBg = Color(0xFFB7B3F7); + static const checkboxDisabledIcon = Color(0xFFFFFFFF); + + // ToggleSwitch + + static const ToggleSwitchBg = Color(0xFFE2DFFF); + static const ToggleSwitchTextInactive = Color(0xFF837CA3); + static const ToggleSwitchActiveBg = Color(0xFFFCF8FF); + static const ToggleSwitchTextActive = Color(0xFF4F46F1); static const mainDarkBlue = Color.fromRGBO(3, 37, 65, 1); static const mainLightBlue = Color.fromRGBO(48, 86, 117, 1); diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 1a98636..bbd27be 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -13,4 +13,5 @@ class _SVG { const _SVG(); String get drifterIcon => "assets/images/logo/drifter_logo_circle.svg"; + String get welcomeImage => "assets/images/logo/welcome.svg"; } diff --git a/pubspec.lock b/pubspec.lock index 24ffa05..189d259 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -357,6 +357,14 @@ packages: description: flutter source: sdk version: "0.0.99" + sliding_switch: + dependency: "direct main" + description: + name: sliding_switch + sha256: "049a9582c9bc30913ce4e34eb26063b468acf5fe47a053636bbf950e5b180dc0" + url: "https://pub.dev" + source: hosted + version: "1.1.0" source_span: dependency: transitive description: @@ -405,6 +413,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.16" + toggle_switch: + dependency: "direct main" + description: + name: toggle_switch + sha256: "9e6af1f0c5a97d9de41109dc7b9e1b3bbe73417f89b10e0e44dc834fb493d4cb" + url: "https://pub.dev" + source: hosted + version: "2.1.0" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6b26206..3fe36b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,7 +34,8 @@ dependencies: nostr_tools: ^1.0.7 flutter_secure_storage: ^8.0.0 flutter_svg: ^2.0.5 - + toggle_switch: ^2.1.0 + sliding_switch: ^1.1.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 @@ -63,7 +64,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - assets/images/logo/ + - assets/images/ # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see