diff --git a/assets/images/avatar.png b/assets/images/avatar.png new file mode 100644 index 0000000..70bcf4a Binary files /dev/null and b/assets/images/avatar.png differ diff --git a/assets/images/banner.png b/assets/images/banner.png new file mode 100644 index 0000000..b83bfad Binary files /dev/null and b/assets/images/banner.png differ diff --git a/assets/images/icons/drifter_vector.png b/assets/images/icons/drifter_vector.png new file mode 100644 index 0000000..14ee6b2 Binary files /dev/null and b/assets/images/icons/drifter_vector.png differ diff --git a/assets/images/logo/drifter_logo_white.png b/assets/images/logo/drifter_logo_white.png new file mode 100644 index 0000000..93f3b1f Binary files /dev/null and b/assets/images/logo/drifter_logo_white.png differ 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/assets/images/qr.png b/assets/images/qr.png new file mode 100644 index 0000000..b5f8d9d Binary files /dev/null and b/assets/images/qr.png differ diff --git a/lib.7z b/lib.7z new file mode 100644 index 0000000..89a64d6 Binary files /dev/null and b/lib.7z differ diff --git a/lib/main.dart b/lib/main.dart index b58b6a4..65208db 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,14 @@ import 'dart:io'; +import 'package:drifter/pages/create_account_screen/create_account_screen.dart'; +import 'package:drifter/pages/home_screen/home_screen_widget.dart'; +import 'package:drifter/pages/login_screen/login_screen.dart'; import 'package:drifter/pages/main_screen/main_screen_widget.dart'; +import 'package:drifter/pages/qr_code_screen/qr_code_screen.dart'; +import 'package:drifter/pages/settings_screen/settings_screen.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'; @@ -19,13 +26,25 @@ class MyApp extends StatelessWidget { title: 'Flutter Demo', theme: ThemeData( appBarTheme: const AppBarTheme(backgroundColor: AppColors.background), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom(foregroundColor: AppColors.background)), bottomNavigationBarTheme: const BottomNavigationBarThemeData( backgroundColor: AppColors.background, selectedItemColor: AppColors.mainAccent, unselectedItemColor: Colors.grey, ), ), - home: const Splash(), + routes: { + '/': (context) => const Splash() + // Splash() + , + '/login': (context) => const LoginScreen(), + '/terms': (context) => const TermsOfServiceScreen(), + '/createAccount': (context) => const CreateAccountScreen(), + '/MainScreen': (context) => const MainScreenWidget(), + '/Settings': (context) => const SettingsScreen(), + '/QrCodeScreen': (context) => const QrCodeScreen(), + }, ); } } diff --git a/lib/models/keys.dart b/lib/models/keys.dart index 85335ea..fd23c7f 100644 --- a/lib/models/keys.dart +++ b/lib/models/keys.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:nostr_tools/nostr_tools.dart'; class Keys { @@ -11,3 +12,6 @@ class Keys { class Relay { static final relay = RelayApi(relayUrl: 'wss://relay.damus.io'); } + +final keyController = TextEditingController(); +final formKey = GlobalKey(); diff --git a/lib/models/models.dart b/lib/models/models.dart new file mode 100644 index 0000000..bda3f8b --- /dev/null +++ b/lib/models/models.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:nostr_tools/nostr_tools.dart'; + +class Keys { + static String privateKey = ''; + static String publicKey = ''; + static String nsecKey = ''; + static String npubKey = ''; + static bool keysExist = false; +} + +class Relay { + static final relay = RelayApi(relayUrl: 'wss://relay.damus.io'); +} + +final keyController = TextEditingController(); +final formKey = GlobalKey(); +final userNameController = TextEditingController(); +final nameController = TextEditingController(); +final descriptionController = TextEditingController(); + +class UserData { + static bool isLogin = true; +} diff --git a/lib/pages/create_account_screen/create_account_screen.dart b/lib/pages/create_account_screen/create_account_screen.dart new file mode 100644 index 0000000..b4bf74e --- /dev/null +++ b/lib/pages/create_account_screen/create_account_screen.dart @@ -0,0 +1,252 @@ +import 'package:dart_nostr/dart_nostr.dart'; +import 'package:drifter/models/models.dart'; +import 'package:drifter/pages/home_screen/widgets/message_text_button_widget.dart'; +import 'package:drifter/pages/profile_screen/profile_screen.dart'; +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/widgets/btn_continue.dart'; +import 'package:drifter/theme/app_colors.dart'; +import 'package:drifter/utilities/assets.dart'; +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 CreateAccountScreen extends StatefulWidget { + const CreateAccountScreen({super.key}); + + @override + State createState() => CreateAccountScreenState(); +} + +class CreateAccountScreenState extends State { + final secureStorage = const FlutterSecureStorage(); + + Future generateNewKeys() async { + final newPrivateKey = await Nostr.instance.keysService.generatePrivateKey(); + // final newPrivateKey = keyGenerator.generatePrivateKey(); + + final nsec = + Nostr.instance.keysService.encodePrivateKeyToNsec(newPrivateKey); + + // final nsecDecoded = + // Nostr.instance.keysService.decodeNsecKeyToPrivateKey(nsec); + // assert(nsecDecoded['type'] == 'nsec'); + // assert(nsecDecoded['data'] == newPrivateKey); + + final newPublicKey = await Nostr.instance.keysService + .derivePublicKey(privateKey: newPrivateKey); + // final newPublicKey = keyGenerator.getPublicKey(newPrivateKey); + + final npub = Nostr.instance.keysService.encodePublicKeyToNpub(newPublicKey); + + // final npubDecoded = + // Nostr.instance.keysService.decodeNpubKeyToPublicKey(npub); + // assert(npubDecoded['type'] == 'npub'); + // assert(npubDecoded['data'] == newPublicKey); + + return await addKeyToStorage(newPrivateKey, newPublicKey, nsec, npub); + } + + Future _getKeysFromStorage() async { + // Reading values associated with the " privateKey " and " publicKey " keys from a secure repository + final storedPrivateKey = await secureStorage.read(key: 'privateKey'); + final storedPublicKey = await secureStorage.read(key: 'publicKey'); + + final storedNsecKey = await secureStorage.read(key: 'nsec'); + final storedNpubKey = await secureStorage.read(key: 'npub'); + + // Indicates that both private and public keys are stored in a secure repository, after which, the state variables are updated + if (storedPrivateKey != null && + storedPublicKey != null && + storedNsecKey != null && + storedNpubKey != null) { + setState(() { + Keys.privateKey = storedPrivateKey; + Keys.publicKey = storedPublicKey; + Keys.nsecKey = storedNsecKey; + Keys.npubKey = storedNpubKey; + Keys.keysExist = true; + }); + } + } + + Future addKeyToStorage( + String privateKeyHex, + String publicKeyHex, + String nsecKey, + String npubKey, + ) async { +// Waiting for both write operations to complete + Future.wait([ + secureStorage.write(key: 'privateKey', value: privateKeyHex), + secureStorage.write(key: 'publicKey', value: publicKeyHex), + secureStorage.write(key: 'nsec', value: nsecKey), + secureStorage.write(key: 'npub', value: npubKey), + ]); + + // Updating status variables and starting widget rebuilding + setState(() { + Keys.privateKey = privateKeyHex; + Keys.publicKey = publicKeyHex; + Keys.nsecKey = nsecKey; + Keys.npubKey = npubKey; + Keys.keysExist = true; + }); + +// Returns a boolean value indicating whether the keys were successfully added to the repository or not. + return Keys.keysExist; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon( + Icons.arrow_back, + color: AppColors.topNavIconBack, + ), + onPressed: () => Navigator.of(context).pop(), + ), + actions: [ + TextButton( + onPressed: () {}, + child: Text('Skip'), + ) + ], + elevation: 0, + backgroundColor: AppColors.mainBackground, + ), + backgroundColor: AppColors.mainBackground, + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const Text( + 'Create an account', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600), + ), + const SizedBox(height: 13), + const Text( + 'You can change this information later in your Profile.', + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w400), + ), + const SizedBox(height: 24), + TextField( + decoration: InputDecoration( + filled: true, + fillColor: AppColors.textFieldDefaultBg, + labelText: 'Username', + labelStyle: TextStyle(color: AppColors.textFieldDefaultText), + prefixIcon: const Icon(Icons.alternate_email), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), + iconColor: AppColors.textFieldDefaultIconTrail), + controller: userNameController, + ), + const SizedBox(height: 8), + TextField( + decoration: InputDecoration( + filled: true, + fillColor: AppColors.textFieldDefaultBg, + labelText: 'Name (optional)', + labelStyle: TextStyle(color: AppColors.textFieldDefaultText), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), + iconColor: AppColors.textFieldDefaultIconTrail), + controller: nameController, + ), + const SizedBox(height: 8), + SizedBox( + height: 112, + child: TextField( + minLines: 3, + maxLines: 5, + decoration: InputDecoration( + filled: true, + fillColor: AppColors.textFieldDefaultBg, + labelText: 'Description (optional)', + alignLabelWithHint: true, + labelStyle: + TextStyle(color: AppColors.textFieldDefaultText), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), + iconColor: AppColors.textFieldDefaultIconTrail), + controller: descriptionController, + ), + ), + Expanded(child: SizedBox()), + SizedBox( + width: double.infinity, + height: 56, + child: ElevatedButton( + onPressed: () { + final currentContext = context; + generateNewKeys().then( + (keysGenerated) { + if (keysGenerated) { + ScaffoldMessenger.of(currentContext).showSnackBar( + MessageSnackBar(label: 'Keys Generated!')); + } + }, + ); + Navigator.pushNamedAndRemoveUntil( + context, '/MainScreen', (_) => false); + }, + 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( + style: TextStyle(color: AppColors.noteText), + 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/home_screen/home_screen_widget.dart b/lib/pages/home_screen/home_screen_widget.dart index 175f735..c1228f0 100644 --- a/lib/pages/home_screen/home_screen_widget.dart +++ b/lib/pages/home_screen/home_screen_widget.dart @@ -3,13 +3,14 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:drifter/domain_models/domain_models.dart'; -import 'package:drifter/models/keys.dart'; +import 'package:drifter/models/models.dart'; import 'package:drifter/theme/app_colors.dart'; import 'package:drifter/pages/home_screen/widgets/message_ok_button_widget.dart'; import 'package:drifter/pages/home_screen/widgets/message_text_button_widget.dart'; import 'package:drifter/pages/home_screen/widgets/message_text_form_field_widget.dart'; import 'package:drifter/pages/profile_screen/widgets/message_snack_bar.dart'; import 'package:nostr_tools/nostr_tools.dart'; +import 'package:toggle_switch/toggle_switch.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -20,6 +21,7 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { bool _isConnected = false; + List isSelected = [true, false]; final List _events = []; final Map _metaDatas = {}; @@ -90,47 +92,128 @@ class _HomeScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: AppColors.mainBackground, body: RefreshIndicator( onRefresh: () async { await _resubscribeStream(); }, - child: StreamBuilder( - stream: _controller.stream, - builder: (context, snapshot) { - // Inside the builder callback, the snapshot object contains the most recent event from the thread. - // If snapshot.hasData is true, there is data to display. In this case, ListView.builder is returned, which displays a list of NoostCard widgets. - if (snapshot.hasData) { - return ListView.builder( - // The itemCount property of ListView.builder is set to _events.length, , which is the number of events in the _events list. - itemCount: _events.length, - itemBuilder: (context, index) { - final event = _events[index]; - final metadata = _metaDatas[event.pubkey]; - // For each event, a Noost object is created that encapsulates the details of the event, including id, avatarUrl, name,username, time, content и pubkey. - // _metaDatas, you can map the event public key to the author's metadata. - final domain = Domain( - noteId: event.id, - avatarUrl: metadata?.picture ?? - 'https://robohash.org/${event.pubkey}', - name: metadata?.name ?? 'Anon', - username: metadata?.displayName ?? - (metadata?.display_name ?? 'Anon'), - time: TimeAgo.format(event.created_at), - content: event.content, - pubkey: event.pubkey, - ); - return DomainCard(domain: domain); + child: Column( + children: [ + // ToggleButtons( + // isSelected: isSelected, + // renderBorder: false, + // fillColor: Color(0xFFFFFFFF), + // selectedColor: Color(0xFF4F46F1), + // disabledColor: Color(0xFF837CA3), + // children: [ + // Container( + // margin: const EdgeInsets.all(4), + // child: const Text('Following')), + // Container( + // margin: const EdgeInsets.all(4), + // child: const Text('Global')), + // ], + // onPressed: (int newIndex) { + // setState(() { + // for (int index = 0; index < isSelected.length; index++) { + // if (index == newIndex) { + // isSelected[index] = true; + // } else { + // isSelected[index] = false; + // } + // } + // }); + // }), + ToggleSwitch( + minWidth: double.infinity, + minHeight: 40, + totalSwitches: 2, + labels: ['Following', 'Global'], + activeBgColor: [AppColors.toggleSwitchActiveBg], + activeFgColor: AppColors.toggleSwitchTextActive, + inactiveBgColor: AppColors.toggleSwitchBg, + inactiveFgColor: AppColors.toggleSwitchTextInactive, + activeBorders: [ + Border.all( + color: AppColors.toggleSwitchBg, + width: 4, + ), + ], + radiusStyle: true, + cornerRadius: 100, + customTextStyles: [ + TextStyle(fontSize: 14, fontWeight: FontWeight.w600) + ], + onToggle: (indexToggle) { + print(indexToggle); + }, + ), + SizedBox( + height: 12, + ), + Flexible( + child: StreamBuilder( + stream: _controller.stream, + builder: (context, snapshot) { + // Inside the builder callback, the snapshot object contains the most recent event from the thread. + // If snapshot.hasData is true, there is data to display. In this case, ListView.builder is returned, which displays a list of NoostCard widgets. + if (snapshot.hasData) { + return ListView.builder( + // The itemCount property of ListView.builder is set to _events.length, , which is the number of events in the _events list. + itemCount: _events.length, + itemBuilder: (context, index) { + final event = _events[index]; + final metadata = _metaDatas[event.pubkey]; + // For each event, a Noost object is created that encapsulates the details of the event, including id, avatarUrl, name,username, time, content и pubkey. + // _metaDatas, you can map the event public key to the author's metadata. + final domain = Domain( + noteId: event.id, + avatarUrl: metadata?.picture ?? + 'https://robohash.org/${event.pubkey}', + name: metadata?.name ?? 'Anon', + username: metadata?.displayName ?? + (metadata?.display_name ?? 'Anon'), + time: TimeAgo.format(event.created_at), + content: event.content, + pubkey: event.pubkey, + ); + return DomainCard(domain: domain); + }, + ); + } else if (snapshot.connectionState == + ConnectionState.waiting) { + return const Center(child: Text('Loading....')); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } + return const CenteredCircularProgressIndicator(); }, - ); - } else if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: Text('Loading....')); - } else if (snapshot.hasError) { - return Center(child: Text('Error: ${snapshot.error}')); - } - return const CenteredCircularProgressIndicator(); - }, + ), + ), + ], ), ), + + // ToggleButtons( + // isSelected: isSelected, + // children: [ + // SizedBox(width: 300, child: Text('Following')), + // SizedBox(child: Text('Global')), + // ], + // onPressed: (int newIndex) { + // setState(() { + // for (int index = 0; + // index < isSelected.length; + // index++) { + // if (index == newIndex) { + // isSelected[index] = true; + // } else { + // isSelected[index] = false; + // } + // } + // }); + // }), + floatingActionButton: Keys.keysExist ? CreatePost( // The publishNote function is called when the user launches the "Noost!" button in the dialog box. @@ -192,16 +275,16 @@ class TimeAgo { String timeAgo = ''; if (difference.inDays > 0) { - timeAgo = - '${difference.inDays} ${difference.inDays == 1 ? 'day' : 'days'} ago'; + timeAgo = '${difference.inDays}d'; + // '${difference.inDays} ${difference.inDays == 1 ? 'day' : 'days'} ago'; } else if (difference.inHours > 0) { - timeAgo = - '${difference.inHours} ${difference.inHours == 1 ? 'hour' : 'hours'} ago'; + timeAgo = '${difference.inHours}h'; + // '${difference.inHours} ${difference.inHours == 1 ? 'hour' : 'hours'} ago'; } else if (difference.inMinutes > 0) { - timeAgo = - '${difference.inMinutes} ${difference.inMinutes == 1 ? 'minute' : 'minutes'} ago'; + timeAgo = '${difference.inMinutes}m'; + // '${difference.inMinutes} ${difference.inMinutes == 1 ? 'minute' : 'minutes'} ago'; } else { - timeAgo = 'just now'; + timeAgo = '${difference.inMinutes}m'; } return timeAgo; @@ -235,44 +318,81 @@ class DomainCard extends StatelessWidget { Widget build(BuildContext context) { final List? imageLinks = extractImage(domain.content); return Container( - margin: const EdgeInsets.all(8), + margin: const EdgeInsets.all(4), decoration: BoxDecoration( - color: AppColors.mainLightBlue, - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 3), - ), - ], + color: AppColors.postBg, + borderRadius: BorderRadius.circular(8), ), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Row( + // children: [ + // CircleAvatar( + // radius: 25, + // backgroundImage: FadeInImage( + // placeholder: const NetworkImage( + // 'https://i.ibb.co/mJkxDkb/satoshi.png'), + // image: NetworkImage(domain.avatarUrl), + // ).image, + // ), + // Container( + // width: 300, + // child: Row( + // children: [ + // Text(domain.name), + // SizedBox( + // width: 4, + // ), + // Text('@${domain.username.toLowerCase()}', + // style: TextStyle(color: AppColors.postUserName)), + // Expanded(child: SizedBox()), + // Text('${domain.time}', + // style: TextStyle(color: AppColors.postUserName)), + // ], + // ), + // ) + // ], + // ), ListTile( + contentPadding: EdgeInsets.only(top: 16, right: 16, left: 16), leading: CircleAvatar( + radius: 25, backgroundImage: FadeInImage( placeholder: const NetworkImage('https://i.ibb.co/mJkxDkb/satoshi.png'), image: NetworkImage(domain.avatarUrl), ).image, ), - title: - Text(domain.name, style: const TextStyle(color: Colors.white)), - subtitle: Text('@${domain.username.toLowerCase()} • ${domain.time}', - style: TextStyle(color: Colors.grey.shade400)), - trailing: const Icon(Icons.more_vert, color: Colors.grey), + title: Row( + children: [ + Text(domain.name), + SizedBox( + width: 4, + ), + Expanded( + child: Container( + child: Text('@${domain.username.toLowerCase()}', + overflow: TextOverflow.ellipsis, + style: TextStyle(color: AppColors.postUserName)), + ), + ), + Text('${domain.time}', + style: TextStyle(color: AppColors.postUserName)), + ], + ), + trailing: + const Icon(Icons.more_horiz, color: AppColors.postMoreIcon), ), - Divider(height: 1, color: Colors.grey.shade400), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: + const EdgeInsets.only(top: 12, right: 16, bottom: 14, left: 78), child: Text(domain.content, - style: const TextStyle(color: Colors.white)), + style: const TextStyle(color: AppColors.postBodyText)), ), if (imageLinks != null && imageLinks.isNotEmpty) - Center( + Padding( + padding: const EdgeInsets.only( + top: 12, right: 16, bottom: 14, left: 78), child: Stack( children: [ const Placeholder( @@ -280,12 +400,15 @@ class DomainCard extends StatelessWidget { color: Colors.transparent, ), Center( - child: FadeInImage( - placeholder: const NetworkImage( - 'https://i.ibb.co/D9jqXgR/58038897-167f0280-7ae6-11e9-94eb-88e880a25f0f.gif', + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: FadeInImage( + placeholder: const NetworkImage( + 'https://media.tenor.com/On7kvXhzml4AAAAj/loading-gif.gif', + ), + image: NetworkImage(imageLinks.first), + fit: BoxFit.cover, ), - image: NetworkImage(imageLinks.first), - fit: BoxFit.cover, ), ), ], @@ -372,7 +495,7 @@ class _CreatePostState extends State { padding: const EdgeInsets.symmetric(vertical: 24), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - color: AppColors.mainDarkBlue, + color: AppColors.background, ), child: const Center( child: Text( diff --git a/lib/pages/login_screen/login_screen.dart b/lib/pages/login_screen/login_screen.dart new file mode 100644 index 0000000..11fe95c --- /dev/null +++ b/lib/pages/login_screen/login_screen.dart @@ -0,0 +1,252 @@ +import 'package:dart_nostr/dart_nostr.dart'; +import 'package:drifter/models/models.dart'; +import 'package:drifter/pages/home_screen/widgets/message_text_button_widget.dart'; +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/widgets/btn_continue.dart'; + +import 'package:drifter/theme/app_colors.dart'; +import 'package:drifter/utilities/assets.dart'; +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}); + + @override + State createState() => LoginScreenState(); +} + +class LoginScreenState extends State { + final keyGenerator = KeyApi(); + final nip19 = Nip19(); + final indexToggle = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon( + Icons.arrow_back, + color: AppColors.topNavIconBack, + ), + onPressed: () => Navigator.of(context).pop(), + ), + actions: [ + TextButton( + onPressed: () {}, + child: Text('Skip'), + ) + ], + elevation: 0, + backgroundColor: AppColors.mainBackground, + ), + backgroundColor: AppColors.mainBackground, + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const SizedBox( + height: 56, + ), + 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, + ), + ], + radiusStyle: true, + cornerRadius: 8, + customTextStyles: [ + TextStyle(fontSize: 14, fontWeight: FontWeight.w600) + ], + onToggle: (indexToggle) { + print(indexToggle); + }, + ), + 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: InputDecoration( + filled: true, + fillColor: AppColors.textFieldDefaultBg, + labelText: 'nsec... / hex...', + labelStyle: + TextStyle(color: AppColors.textFieldDefaultText), + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.paste), + color: AppColors.textFieldActiveIconTrail, + onPressed: () {}, + ), + IconButton( + icon: const Icon(Icons.qr_code_scanner), + color: AppColors.textFieldActiveIconTrail, + onPressed: () {}, + ), + ], + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), + iconColor: AppColors.textFieldDefaultIconTrail), + 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( + style: TextStyle(color: AppColors.noteText), + 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/main_screen/main_screen_widget.dart b/lib/pages/main_screen/main_screen_widget.dart index 06c478c..de04474 100644 --- a/lib/pages/main_screen/main_screen_widget.dart +++ b/lib/pages/main_screen/main_screen_widget.dart @@ -1,8 +1,12 @@ // import 'package:drifter/pages/home_screen/home_screen_widget.dart'; import 'package:drifter/pages/home_screen/home_screen_widget.dart'; +import 'package:drifter/pages/login_screen/login_screen.dart'; import 'package:drifter/pages/message_screen/message_screen_widget.dart'; +import 'package:drifter/pages/notifications_screen/notifications_screen.dart'; import 'package:drifter/pages/profile_screen/profile_screen.dart'; +import 'package:drifter/pages/search_screen/search_screen.dart'; import 'package:drifter/theme/app_colors.dart'; +import 'package:drifter/main.dart'; import 'package:drifter/utilities/assets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; @@ -15,66 +19,123 @@ class MainScreenWidget extends StatefulWidget { } class _MainScreenWidgetState extends State { - int _selectedTap = 0; + int _selectedTap = 2; + late String _title; void onSelectedtap(int index) { if (_selectedTap == index) return; setState(() { _selectedTap = index; + switch (index) { + case 0: + { + _title = 'Message'; + } + break; + case 1: + { + _title = 'Search'; + } + break; + case 2: + { + _title = 'Feed'; + } + break; + case 3: + { + _title = 'Notifications'; + } + break; + case 4: + { + _title = 'Profile'; + } + break; + } }); } + @override + initState() { + _title = 'Feed'; + } + @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.mainBackground, 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( - "Drifter", - style: TextStyle( - color: AppColors.mainAccent, - ), - // textAlign: TextAlign.center, - ), - ], + leading: IconButton( + icon: Icon(Icons.settings), + color: AppColors.topNavIconPtimary, + onPressed: () { + Navigator.pushNamed(context, '/Settings'); + }, + ), + actions: [ + IconButton( + icon: Icon(Icons.rss_feed), + color: AppColors.topNavIconPtimary, + onPressed: () {}, + ), + ], + title: Text( + _title, + style: TextStyle( + color: AppColors.topNavText, + ), + // textAlign: TextAlign.center, ), centerTitle: true, + backgroundColor: AppColors.mainBackground, + elevation: 0, ), body: IndexedStack( index: _selectedTap, children: const [ - HomeScreen(), MessageScreen(), + SearchScreen(), + HomeScreen(), + NotificationsScreen(), ProfileScreen(), + // LoginScreen(), ], ), bottomNavigationBar: BottomNavigationBar( + backgroundColor: AppColors.bottomNavBackground, + selectedItemColor: AppColors.bottomNavIconActive, + unselectedItemColor: AppColors.bottomNavIconDefault, + selectedFontSize: 0, currentIndex: _selectedTap, + type: BottomNavigationBarType.fixed, items: const [ BottomNavigationBarItem( - icon: Icon(Icons.home), - label: 'Home', + icon: Icon(Icons.mail_outline), + label: '', ), BottomNavigationBarItem( - icon: Icon(Icons.message), - label: 'Message', + icon: Icon(Icons.search), + label: '', ), BottomNavigationBarItem( - icon: Icon(Icons.person), - label: 'Profile', + icon: + ImageIcon(AssetImage('assets/images/icons/drifter_vector.png')), + label: '', ), + BottomNavigationBarItem( + icon: Icon(Icons.notifications_outlined), + label: '', + ), + + BottomNavigationBarItem( + icon: Icon(Icons.person_2_outlined), + label: '', + ), + // BottomNavigationBarItem( + // icon: Icon(Icons.login), + // label: 'Login', + // ), ], onTap: onSelectedtap, ), diff --git a/lib/pages/message_screen/message_screen_widget.dart b/lib/pages/message_screen/message_screen_widget.dart index ced3ed2..d149c5f 100644 --- a/lib/pages/message_screen/message_screen_widget.dart +++ b/lib/pages/message_screen/message_screen_widget.dart @@ -18,7 +18,7 @@ class _MessageScreenState extends State { children: const [ Center( child: Text( - "List of posts", + "Message", ), ) ], diff --git a/lib/pages/notifications_screen/notifications_screen.dart b/lib/pages/notifications_screen/notifications_screen.dart new file mode 100644 index 0000000..26a33e7 --- /dev/null +++ b/lib/pages/notifications_screen/notifications_screen.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/placeholder.dart'; + +class NotificationsScreen extends StatefulWidget { + const NotificationsScreen({super.key}); + + @override + State createState() => _NotificationsScreenState(); +} + +class _NotificationsScreenState extends State { + @override + Widget build(BuildContext context) { + return Center( + child: Text('Notifications Page'), + ); + } +} diff --git a/lib/pages/profile_screen/profile_screen.dart b/lib/pages/profile_screen/profile_screen.dart index a1eaf26..330e025 100644 --- a/lib/pages/profile_screen/profile_screen.dart +++ b/lib/pages/profile_screen/profile_screen.dart @@ -1,6 +1,7 @@ import 'package:dart_nostr/dart_nostr.dart'; -import 'package:drifter/models/keys.dart'; +import 'package:drifter/models/models.dart'; import 'package:drifter/pages/profile_screen/widgets/delete_keys_dialog.dart'; +import 'package:drifter/main.dart'; import 'package:drifter/pages/profile_screen/widgets/key_exist_dialog.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'; @@ -17,7 +18,8 @@ class ProfileScreen extends StatefulWidget { } class ProfileScreenState extends State { - final _secureStorage = const FlutterSecureStorage(); + final secureStorage = const FlutterSecureStorage(); + bool _toHex = false; TextEditingController privateKeyInput = TextEditingController(); TextEditingController publicKeyInput = TextEditingController(); @@ -37,8 +39,9 @@ class ProfileScreenState extends State { final nsec = Nostr.instance.keysService.encodePrivateKeyToNsec(newPrivateKey); - final nsecDecoded = - Nostr.instance.keysService.decodeNsecKeyToPrivateKey(nsec); + + // final nsecDecoded = + // Nostr.instance.keysService.decodeNsecKeyToPrivateKey(nsec); // assert(nsecDecoded['type'] == 'nsec'); // assert(nsecDecoded['data'] == newPrivateKey); @@ -47,21 +50,22 @@ class ProfileScreenState extends State { // final newPublicKey = keyGenerator.getPublicKey(newPrivateKey); final npub = Nostr.instance.keysService.encodePublicKeyToNpub(newPublicKey); - final npubDecoded = - Nostr.instance.keysService.decodeNpubKeyToPublicKey(npub); + + // final npubDecoded = + // Nostr.instance.keysService.decodeNpubKeyToPublicKey(npub); // assert(npubDecoded['type'] == 'npub'); // assert(npubDecoded['data'] == newPublicKey); - return await _addKeyToStorage(newPrivateKey, newPublicKey, nsec, npub); + return await addKeyToStorage(newPrivateKey, newPublicKey, nsec, npub); } Future _getKeysFromStorage() async { // Reading values associated with the " privateKey " and " publicKey " keys from a secure repository - final storedPrivateKey = await _secureStorage.read(key: 'privateKey'); - final storedPublicKey = await _secureStorage.read(key: 'publicKey'); + final storedPrivateKey = await secureStorage.read(key: 'privateKey'); + final storedPublicKey = await secureStorage.read(key: 'publicKey'); - final storedNsecKey = await _secureStorage.read(key: 'nsec'); - final storedNpubKey = await _secureStorage.read(key: 'npub'); + final storedNsecKey = await secureStorage.read(key: 'nsec'); + final storedNpubKey = await secureStorage.read(key: 'npub'); // Indicates that both private and public keys are stored in a secure repository, after which, the state variables are updated if (storedPrivateKey != null && @@ -80,7 +84,7 @@ class ProfileScreenState extends State { // Adding a new key // Writing a private and public key to a secure vault - Future _addKeyToStorage( + Future addKeyToStorage( String privateKeyHex, String publicKeyHex, String nsecKey, @@ -88,10 +92,10 @@ class ProfileScreenState extends State { ) async { // Waiting for both write operations to complete Future.wait([ - _secureStorage.write(key: 'privateKey', value: privateKeyHex), - _secureStorage.write(key: 'publicKey', value: privateKeyHex), - _secureStorage.write(key: 'nsec', value: nsecKey), - _secureStorage.write(key: 'npub', value: npubKey), + secureStorage.write(key: 'privateKey', value: privateKeyHex), + secureStorage.write(key: 'publicKey', value: publicKeyHex), + secureStorage.write(key: 'nsec', value: nsecKey), + secureStorage.write(key: 'npub', value: npubKey), ]); // Updating status variables and starting widget rebuilding @@ -110,10 +114,10 @@ class ProfileScreenState extends State { Future _deleteKeysStorage() async { // Calling secure storage to remove keys from storage Future.wait([ - _secureStorage.delete(key: 'privateKey'), - _secureStorage.delete(key: 'publicKey'), - _secureStorage.delete(key: 'nsec'), - _secureStorage.delete(key: 'npub'), + secureStorage.delete(key: 'privateKey'), + secureStorage.delete(key: 'publicKey'), + secureStorage.delete(key: 'nsec'), + secureStorage.delete(key: 'npub'), ]); // Updating status variables, resetting values after deleting keys from the repository @@ -128,69 +132,390 @@ class ProfileScreenState extends State { @override Widget build(BuildContext context) { - privateKeyInput.text = Keys.nsecKey; publicKeyInput.text = Keys.npubKey; - return ListView( - children: [ - SizedBox( - height: 60, - ), - UserInfo(), - SizedBox( - height: 40, - ), - FormKeys(), - SizedBox(height: 20), - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + return Container( + color: AppColors.profileWrapperBg, + child: Column( + children: [ + Stack( children: [ - Keys.keysExist - ? ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - AppColors.mainDarkBlue)), - onPressed: () { - keysExistDialog( - Nostr.instance.keysService - .encodePublicKeyToNpub(Keys.publicKey), - Nostr.instance.keysService - .encodePrivateKeyToNsec(Keys.privateKey), - ); - }, - child: Text( - 'Keys', - ), - ) - : ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - AppColors.mainDarkBlue)), - onPressed: () { - modalBottomSheet(); - }, - child: Text( - 'Generate Keys', + Container( + width: double.infinity, + height: 70, + child: FittedBox( + child: Image.asset('assets/images/banner.png'), + fit: BoxFit.fill, + ), + ), + Row( + children: [ + Padding( + padding: + const EdgeInsets.only(top: 30, left: 16, right: 58), + child: ClipRRect( + borderRadius: BorderRadius.circular(200), + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + border: Border.all( + width: 2, color: Color(0xFFF2EFFF))), + child: Image.asset('assets/images/avatar.png')), + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 74, right: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + width: 32, + height: 32, + child: FloatingActionButton( + backgroundColor: + AppColors.buttonSecondaryDefaultBg, + onPressed: () {}, + elevation: 0, + child: Icon( + Icons.edit, + size: 18, + color: AppColors.buttonSecondaryIcon, + ), + ), + ), + SizedBox( + width: 8, + ), + SizedBox( + width: 32, + height: 32, + child: FloatingActionButton( + backgroundColor: + AppColors.buttonSecondaryDefaultBg, + onPressed: () { + Navigator.pushNamed(context, '/QrCodeScreen'); + }, + elevation: 0, + child: Icon( + Icons.qr_code, + size: 18, + color: AppColors.buttonSecondaryIcon, + ), + ), + ), + SizedBox( + width: 8, + ), + SizedBox( + width: 32, + height: 32, + child: FloatingActionButton( + backgroundColor: + AppColors.buttonSecondaryDefaultBg, + onPressed: () {}, + elevation: 0, + child: Icon( + Icons.mail_outlined, + size: 18, + color: AppColors.buttonSecondaryIcon, + ), + ), + ), + SizedBox( + width: 8, + ), + SizedBox( + width: 100, + height: 32, + child: ElevatedButton( + onPressed: () {}, + child: Text( + 'Follow', + style: TextStyle( + color: + AppColors.buttonPrimaryDefaultText), + ), + style: ElevatedButton.styleFrom( + backgroundColor: + AppColors.buttonPrimaryDefaultBg, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(100)))), + ) + ], ), ), - Keys.keysExist - ? Row( - children: [ - IconButton( - onPressed: () { - deleteKeysDialog(); - }, - icon: const Icon(Icons.delete)), - ], - ) - : Container(), + ) + ], + ) ], ), - ) - ], + Container( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Cameron Williamson', + style: TextStyle( + color: AppColors.profileFullName, + fontWeight: FontWeight.w600), + ), + SizedBox( + height: 5, + ), + Text('@cameron', + style: TextStyle( + color: AppColors.profileUserName, + )), + SizedBox( + height: 5, + ), + Text( + 'This is the description of my profile. I am a nostrich and I love cats. Follow me for fun pictures and bad jokes. I also want to be a politician.', + style: TextStyle( + color: AppColors.profileSummary, + )), + SizedBox( + height: 12, + ), + Container( + child: TextField( + style: TextStyle(color: AppColors.profileKeyText), + readOnly: true, + decoration: InputDecoration( + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.copy), + color: AppColors.buttonSecondaryIcon, + onPressed: () {}, + ), + IconButton( + icon: Icon(Icons.qr_code), + color: AppColors.buttonSecondaryIcon, + onPressed: () {}, + ), + ], + ), + filled: true, + fillColor: AppColors.profileKeyField, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: + BorderSide(width: 0, style: BorderStyle.none), + )), + minLines: 2, + maxLines: 2, + controller: publicKeyInput, + ), + ), + Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextButton.icon( + // <-- TextButton + onPressed: () {}, + icon: Icon( + Icons.check_circle, + size: 15.0, + color: AppColors.profileSocialIcons, + ), + label: Text( + 'nip-05-name', + style: TextStyle( + color: AppColors.profileSocialLinks, + fontWeight: FontWeight.w500), + ), + ), + SizedBox( + height: 6, + ), + TextButton.icon( + // <-- TextButton + onPressed: () {}, + icon: Icon( + Icons.check_circle, + size: 15.0, + color: AppColors.profileSocialIcons, + ), + label: Text( + 'www.cameronforpresident.com', + style: TextStyle( + color: AppColors.profileSocialLinks, + fontWeight: FontWeight.w500), + ), + ), + SizedBox( + height: 6, + ), + TextButton.icon( + // <-- TextButton + onPressed: () {}, + icon: Icon( + Icons.score, + size: 15.0, + color: AppColors.profileSocialIcons, + ), + label: Text( + '@cameron_official', + style: TextStyle( + color: AppColors.profileSocialLinks, + fontWeight: FontWeight.w500), + ), + ), + SizedBox( + height: 6, + ), + TextButton.icon( + // <-- TextButton + onPressed: () {}, + icon: Icon( + Icons.check_circle, + size: 15.0, + color: AppColors.profileSocialIcons, + ), + label: Text( + 'cameron.williamson.com/@cameron_official', + style: TextStyle( + color: AppColors.profileSocialLinks, + fontWeight: FontWeight.w500), + ), + ), + SizedBox( + height: 6, + ), + TextButton.icon( + // <-- TextButton + onPressed: () {}, + icon: Icon( + Icons.flash_on, + size: 15.0, + color: AppColors.profileSocialIcons, + ), + label: Text( + 'whoknowswhatgoeshere', + style: TextStyle( + color: AppColors.profileSocialLinks, + fontWeight: FontWeight.w500), + ), + ), + SizedBox( + height: 6, + ), + TextButton.icon( + // <-- TextButton + onPressed: () {}, + icon: Icon( + Icons.flash_on, + size: 15.0, + color: AppColors.profileSocialIcons, + ), + label: Text( + 'cameron@satoshiwallet.com', + style: TextStyle( + color: AppColors.profileSocialLinks, + fontWeight: FontWeight.w500), + ), + ), + ]), + ) + ]), + ), + ), + ], + ), ); + // return ListView( + // children: [ + // const SizedBox( + // height: 60, + // ), + // const UserInfo(), + // const SizedBox( + // height: 40, + // ), + // FormKeys(), + // const SizedBox(height: 20), + // Padding( + // padding: const EdgeInsets.all(16.0), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceAround, + // children: [ + // Keys.keysExist + // ? IconButton( + // onPressed: () { + // setState(() { + // _toHex = !_toHex; + // }); + // }, + // icon: const Icon(Icons.refresh)) + // // ElevatedButton( + // // style: ButtonStyle( + // // backgroundColor: + // // MaterialStateProperty.all(AppColors.background)), + // // onPressed: () { + // // keysExistDialog( + // // Nostr.instance.keysService + // // .encodePublicKeyToNpub(Keys.publicKey), + // // Nostr.instance.keysService + // // .encodePrivateKeyToNsec(Keys.privateKey), + // // ); + // // }, + // // child: const Text( + // // 'Keys', + // // ), + // // ) + // : Row( + // children: [ + // ElevatedButton( + // style: ButtonStyle( + // backgroundColor: MaterialStateProperty.all( + // AppColors.background)), + // onPressed: () { + // modalBottomSheet(); + // }, + // child: const Text( + // 'Generate Keys', + // ), + // ), + // SizedBox(width: 100), + // ElevatedButton( + // style: ButtonStyle( + // backgroundColor: MaterialStateProperty.all( + // AppColors.background)), + // onPressed: () { + // Navigator.pushNamed(context, '/login').then((_) { + // initState(); + // }); + // }, + // child: const Text( + // 'Login', + // ), + // ), + // ], + // ), + // Keys.keysExist + // ? Row( + // children: [ + // IconButton( + // onPressed: () { + // deleteKeysDialog(); + // }, + // icon: const Icon(Icons.delete)), + // ], + // ) + // : Container(), + // ], + // ), + // ) + // ], + // ); } Form FormKeys() { diff --git a/lib/pages/profile_screen/profile_screen_widgets/delete_keys_dialog.dart b/lib/pages/profile_screen/profile_screen_widgets/delete_keys_dialog.dart deleted file mode 100644 index 8e4874f..0000000 --- a/lib/pages/profile_screen/profile_screen_widgets/delete_keys_dialog.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:drifter/theme/app_colors.dart'; - -import 'ok_button_widget.dart'; - -class DeleteKeysDialog extends StatelessWidget { - const DeleteKeysDialog({ - super.key, - required this.onNoPressed, - required this.onYesPressed, - }); - - final void Function()? onNoPressed; - final void Function()? onYesPressed; - - @override - Widget build(BuildContext context) { - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: Container( - constraints: const BoxConstraints(maxWidth: 600), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.white, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.symmetric(vertical: 24), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.redAccent, - ), - child: const Center( - child: Text( - 'Delete Keys!', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ), - const SizedBox(height: 24), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Do you want to delete your keys?', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[700], - ), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: onNoPressed, - child: Text( - 'On', - style: TextStyle(color: AppColors.mainDarkBlue), - ), - ), - OkButton( - onPressed: onYesPressed, - label: 'YES', - ), - ], - ), - ], - ), - ) - ], - ), - ), - ); - } -} diff --git a/lib/pages/profile_screen/profile_screen_widgets/generated_keys.dart b/lib/pages/profile_screen/profile_screen_widgets/generated_keys.dart deleted file mode 100644 index 4049374..0000000 --- a/lib/pages/profile_screen/profile_screen_widgets/generated_keys.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'ok_button_widget.dart'; - -class GeneratedKeys extends StatefulWidget { - const GeneratedKeys({ - super.key, - required this.npubEncoded, - required this.nsecEncoded, - required this.hexPriv, - required this.hexPub, - }); - - final String npubEncoded; - final String nsecEncoded; - final String hexPriv; - final String hexPub; - - @override - State createState() => _GeneratedKeysState(); -} - -class _GeneratedKeysState extends State { - bool _toHex = false; - - @override - Widget build(BuildContext context) { - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: Container( - constraints: const BoxConstraints(maxWidth: 600), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.white, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.symmetric(vertical: 24), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.indigo, - ), - child: const Center( - child: Text( - 'Keys', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ), - const SizedBox(height: 24), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Public Key', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[700], - ), - ), - const SizedBox(height: 12), - SelectableText( - _toHex ? widget.hexPub : widget.npubEncoded, - style: TextStyle( - fontSize: 16, - color: Colors.grey[800], - ), - ), - const SizedBox(height: 24), - Text( - 'Private Key', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[700], - ), - ), - const SizedBox(height: 12), - SelectableText( - _toHex ? widget.hexPriv : widget.nsecEncoded, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.redAccent, - ), - ), - const SizedBox(height: 24), - Row( - mainAxisAlignment: MainAxisAlignment - .spaceBetween, // Changed to space between to create space for icon buttons - children: [ - IconButton( - onPressed: () { - setState(() { - _toHex = !_toHex; - }); - }, - icon: const Icon(Icons.autorenew_outlined), - color: Colors.grey[700], - ), - OkButton( - onPressed: () { - Navigator.pop(context); - }, - label: 'OK', - ), - ], - ) - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/profile_screen/profile_screen_widgets/key_exist_dialog.dart b/lib/pages/profile_screen/profile_screen_widgets/key_exist_dialog.dart deleted file mode 100644 index d74e34f..0000000 --- a/lib/pages/profile_screen/profile_screen_widgets/key_exist_dialog.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:drifter/theme/app_colors.dart'; - -import 'ok_button_widget.dart'; - -class KeysExistDialog extends StatefulWidget { - const KeysExistDialog({ - super.key, - required this.npubEncoded, - required this.nsecEncoded, - required this.hexPriv, - required this.hexPub, - }); - - final String npubEncoded; - final String nsecEncoded; - final String hexPriv; - final String hexPub; - - @override - State createState() => _KeysExistDialogState(); -} - -class _KeysExistDialogState extends State { - bool _toHex = false; - - @override - Widget build(BuildContext context) { - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: Container( - constraints: const BoxConstraints(maxWidth: 600), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.white, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.symmetric(vertical: 24), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: AppColors.mainDarkBlue, - ), - child: const Center( - child: Text( - 'Keys', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ), - const SizedBox(height: 24), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Public Key', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[700], - ), - ), - const SizedBox(height: 12), - SelectableText( - _toHex ? widget.hexPub : widget.npubEncoded, - style: TextStyle( - fontSize: 16, - color: Colors.grey[800], - ), - ), - const SizedBox(height: 24), - Text( - 'Private Key', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[700], - ), - ), - const SizedBox(height: 12), - SelectableText( - _toHex ? widget.hexPriv : widget.nsecEncoded, - style: TextStyle( - fontSize: 16, - color: Colors.grey[800], - ), - ), - const SizedBox(height: 24), - Row( - mainAxisAlignment: MainAxisAlignment - .spaceBetween, // Changed to space between to create space for icon buttons - children: [ - IconButton( - onPressed: () { - setState(() { - _toHex = !_toHex; - }); - }, - icon: const Icon(Icons.autorenew_outlined), - color: Colors.grey[700], - ), - OkButton( - onPressed: () { - Navigator.pop(context); - }, - label: 'OK', - ), - ], - ) - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/profile_screen/profile_screen_widgets/keys_option_modal_bottom_sheet.dart b/lib/pages/profile_screen/profile_screen_widgets/keys_option_modal_bottom_sheet.dart deleted file mode 100644 index 25ad2e5..0000000 --- a/lib/pages/profile_screen/profile_screen_widgets/keys_option_modal_bottom_sheet.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:drifter/theme/app_colors.dart'; - -class KeysOptionModalBottomSheet extends StatelessWidget { - const KeysOptionModalBottomSheet({ - super.key, - required this.generateNewKeyPressed, - }); - - final void Function()? generateNewKeyPressed; - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration(color: AppColors.mainDarkBlue), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(Colors.white)), - onPressed: generateNewKeyPressed, - child: Text( - 'Generate New Key', - style: TextStyle(color: AppColors.mainDarkBlue), - ), - ), - const SizedBox(height: 10), - ], - ), - ); - } -} diff --git a/lib/pages/profile_screen/profile_screen_widgets/message_snack_bar.dart b/lib/pages/profile_screen/profile_screen_widgets/message_snack_bar.dart deleted file mode 100644 index d5c4fd1..0000000 --- a/lib/pages/profile_screen/profile_screen_widgets/message_snack_bar.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/material.dart'; - -class MessageSnackBar extends SnackBar { - MessageSnackBar({Key? key, required this.label, this.isWarning = false}) - : super( - key: key, - content: _GenericErrorSnackBarMessage( - label: label, - isWarning: isWarning, - ), - backgroundColor: isWarning! ? Colors.red : Colors.white, - elevation: 6.0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - behavior: SnackBarBehavior.fixed, - ); - - final String label; - final bool? isWarning; -} - -class _GenericErrorSnackBarMessage extends StatelessWidget { - const _GenericErrorSnackBarMessage({ - Key? key, - required this.label, - this.isWarning, - }) : super(key: key); - - final String label; - final bool? isWarning; - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), - child: Text( - label, - style: TextStyle( - color: isWarning! ? Colors.white : Colors.black, - fontSize: 16.0, - ), - ), - ); - } -} diff --git a/lib/pages/profile_screen/profile_screen_widgets/ok_button_widget.dart b/lib/pages/profile_screen/profile_screen_widgets/ok_button_widget.dart deleted file mode 100644 index 6dbaad7..0000000 --- a/lib/pages/profile_screen/profile_screen_widgets/ok_button_widget.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:drifter/theme/app_colors.dart'; - -class OkButton extends StatelessWidget { - const OkButton({ - super.key, - required this.onPressed, - required this.label, - }); - - final void Function()? onPressed; - final String label; - - @override - Widget build(BuildContext context) { - return ElevatedButton( - onPressed: onPressed, - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.mainDarkBlue, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: Text( - label, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ); - } -} diff --git a/lib/pages/profile_screen/profile_screen_widgets/user_info_widget.dart b/lib/pages/profile_screen/profile_screen_widgets/user_info_widget.dart deleted file mode 100644 index 53c47f4..0000000 --- a/lib/pages/profile_screen/profile_screen_widgets/user_info_widget.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:drifter/theme/app_colors.dart'; -import 'package:flutter/material.dart'; - -class UserInfo extends StatelessWidget { - const UserInfo({super.key}); - - @override - Widget build(BuildContext context) { - return Container( - child: Column( - children: [ - AvatarWidget(), - SizedBox( - height: 10, - ), - UserNameWidget(), - ], - ), - ); - } -} - -class AvatarWidget extends StatelessWidget { - const AvatarWidget({super.key}); - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - border: Border.all(color: Colors.black), - borderRadius: BorderRadius.all(Radius.circular(75))), - width: 150, - height: 150, - ); - } -} - -class UserNameWidget extends StatefulWidget { - const UserNameWidget({super.key}); - - @override - State createState() => _UserNameWidgetState(); -} - -class _UserNameWidgetState extends State { - late final TextEditingController messageController; - late final FocusNode messageFocusNode; - - @override - void initState() { - messageController = TextEditingController(); - messageFocusNode = FocusNode(); - - super.initState(); - } - - @override - void dispose() { - messageController.dispose(); - messageFocusNode.dispose(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(4.0), - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular( - 0.0, - ), - ), - child: TextField( - controller: messageController, - focusNode: messageFocusNode, - style: const TextStyle( - fontSize: 14, - ), - decoration: InputDecoration( - hintText: 'Username', - hintStyle: const TextStyle(fontSize: 14), - suffixIcon: IconButton( - icon: const Icon( - Icons.send, - color: AppColors.mainDarkBlue, - size: 30, - ), - onPressed: () {}, - ), - ), - ), - ), - ); - } -} diff --git a/lib/pages/profile_screen/widgets/key_exist_dialog.dart b/lib/pages/profile_screen/widgets/key_exist_dialog.dart index d74e34f..84230b1 100644 --- a/lib/pages/profile_screen/widgets/key_exist_dialog.dart +++ b/lib/pages/profile_screen/widgets/key_exist_dialog.dart @@ -43,7 +43,7 @@ class _KeysExistDialogState extends State { padding: const EdgeInsets.symmetric(vertical: 24), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - color: AppColors.mainDarkBlue, + color: AppColors.background, ), child: const Center( child: Text( diff --git a/lib/pages/profile_screen/widgets/ok_button_widget.dart b/lib/pages/profile_screen/widgets/ok_button_widget.dart index 6dbaad7..ae7412b 100644 --- a/lib/pages/profile_screen/widgets/ok_button_widget.dart +++ b/lib/pages/profile_screen/widgets/ok_button_widget.dart @@ -16,7 +16,7 @@ class OkButton extends StatelessWidget { return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.mainDarkBlue, + backgroundColor: AppColors.background, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), diff --git a/lib/pages/qr_code_screen/qr_code_screen.dart b/lib/pages/qr_code_screen/qr_code_screen.dart new file mode 100644 index 0000000..06525c2 --- /dev/null +++ b/lib/pages/qr_code_screen/qr_code_screen.dart @@ -0,0 +1,166 @@ +import 'package:drifter/models/models.dart'; +import 'package:drifter/pages/terms_of_service/btn_continue_terms_of_service.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/services.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter/src/widgets/placeholder.dart'; +import 'package:qr_flutter/qr_flutter.dart'; + +class QrCodeScreen extends StatefulWidget { + const QrCodeScreen({super.key}); + + @override + State createState() => _QrCodeScreenState(); +} + +class _QrCodeScreenState extends State { + String _publicKey = Keys.npubKey; + bool isCopied = 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, bottom: 16), + child: Center( + child: Column(children: [ + Text( + 'Follow me on Nostr', + style: TextStyle( + color: Color(0xFF302F38), + fontSize: 20, + fontWeight: FontWeight.w600), + ), + Padding( + padding: const EdgeInsets.only( + top: 24, bottom: 83, right: 48, left: 48), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(32), + color: Colors.white, + ), + height: 406, + width: double.infinity, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 32.0, bottom: 10), + child: ClipRRect( + borderRadius: BorderRadius.circular(200), + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + border: Border.all( + width: 2, color: Color(0xFFF2EFFF))), + child: Image.asset('assets/images/avatar.png')), + ), + ), + Text( + 'Cameron Williams on', + style: TextStyle( + color: Color(0xFF302F38), + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 4), + Text( + '@cameron', + style: TextStyle( + fontSize: 12, color: AppColors.profileUserName), + ), + SizedBox(height: 20), + Container( + width: 230, + child: Text(_publicKey, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppColors.profileSocialLinks))), + SizedBox(height: 20), + Container( + width: 143, + height: 143, + child: QrImageView( + eyeStyle: QrEyeStyle( + color: AppColors.qrCodeBody, + eyeShape: QrEyeShape.square), + dataModuleStyle: + QrDataModuleStyle(color: AppColors.qrCodeBody), + embeddedImage: AssetImage( + 'assets/images/logo/drifter_logo_white.png'), + data: _publicKey, + version: QrVersions.auto, + ), + ), + ], + )), + ), + Expanded(child: SizedBox()), + SizedBox( + width: double.infinity, + height: 56, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100)), + backgroundColor: AppColors.buttonSecondaryDefaultBg), + onPressed: () { + Clipboard.setData(ClipboardData(text: _publicKey)) + .then((value) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Copied to clipboard'), + )); + isCopied = true; + setState(() {}); + }); + }, + child: isCopied + ? Text( + 'Copied', + style: TextStyle(color: AppColors.buttonSecondaryText), + ) + : Text( + 'Copy user ID', + style: TextStyle(color: AppColors.buttonSecondaryText), + ), + ), + ), + SizedBox(height: 12), + SizedBox( + width: double.infinity, + height: 56, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100)), + backgroundColor: AppColors.buttonPrimaryDefaultBg), + onPressed: () {}, + child: Text( + 'Share QR code', + style: TextStyle(color: AppColors.buttonPrimaryDefaultText), + ), + ), + ), + ]), + ), + ), + ); + } +} diff --git a/lib/pages/search_screen/search_screen.dart b/lib/pages/search_screen/search_screen.dart new file mode 100644 index 0000000..30f920d --- /dev/null +++ b/lib/pages/search_screen/search_screen.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/placeholder.dart'; + +class SearchScreen extends StatefulWidget { + const SearchScreen({super.key}); + + @override + State createState() => _SearchScreenState(); +} + +class _SearchScreenState extends State { + @override + Widget build(BuildContext context) { + return Center( + child: Text('Search Page'), + ); + } +} diff --git a/lib/pages/settings_screen/settings_screen.dart b/lib/pages/settings_screen/settings_screen.dart new file mode 100644 index 0000000..062e4ab --- /dev/null +++ b/lib/pages/settings_screen/settings_screen.dart @@ -0,0 +1,41 @@ +import 'package:drifter/theme/app_colors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/placeholder.dart'; + +class SettingsScreen extends StatefulWidget { + const SettingsScreen({super.key}); + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.mainBackground, + appBar: AppBar( + leading: IconButton( + icon: Icon( + Icons.arrow_back, + color: AppColors.topNavIconPtimary, + ), + onPressed: () => Navigator.of(context).pop(), + ), + title: Text( + ('Settings'), + style: TextStyle( + color: AppColors.topNavText, + ), + // textAlign: TextAlign.center, + ), + centerTitle: true, + backgroundColor: AppColors.mainBackground, + elevation: 0, + ), + body: Center( + child: Text('Settings'), + ), + ); + } +} diff --git a/lib/pages/splash_screen/splash_screen.dart b/lib/pages/splash_screen/splash_screen.dart index 1e7a69f..4256876 100644 --- a/lib/pages/splash_screen/splash_screen.dart +++ b/lib/pages/splash_screen/splash_screen.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:drifter/pages/main_screen/main_screen_widget.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'; @@ -18,7 +19,7 @@ class _SplashState extends State { super.initState(); Future.delayed(const Duration(seconds: 3), () { Navigator.pushReplacement(context, - MaterialPageRoute(builder: (context) => const MainScreenWidget())); + MaterialPageRoute(builder: (context) => const WelcomeScreen())); }); } @@ -34,7 +35,7 @@ class _SplashState extends State { height: 300, ), Image.asset( - 'assets/images/logo/drifter_vector.png', + 'assets/images/icons/drifter_vector.png', height: 111, width: 93, ), diff --git a/lib/pages/terms_of_service/btn_continue_terms_of_service.dart b/lib/pages/terms_of_service/btn_continue_terms_of_service.dart new file mode 100644 index 0000000..d878e1c --- /dev/null +++ b/lib/pages/terms_of_service/btn_continue_terms_of_service.dart @@ -0,0 +1,55 @@ +import 'package:drifter/models/models.dart'; +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: () { + UserData.isLogin + ? Navigator.pushNamed(context, '/login') + : Navigator.pushNamed(context, '/createAccount'); + }, + 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/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..1843f4a --- /dev/null +++ b/lib/pages/terms_of_service/terms_of_service.dart @@ -0,0 +1,90 @@ +import 'package:drifter/pages/terms_of_service/btn_continue_terms_of_service.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..95affb3 --- /dev/null +++ b/lib/pages/welcome_screen/welcome_screen.dart @@ -0,0 +1,78 @@ +import 'package:drifter/models/models.dart'; +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: () { + UserData.isLogin = true; + 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: () { + UserData.isLogin = false; + Navigator.pushNamed(context, '/terms'); + }, + 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..aa09de9 100644 --- a/lib/theme/app_colors.dart +++ b/lib/theme/app_colors.dart @@ -1,10 +1,94 @@ 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); + + // Button + 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); + static const buttonSecondaryDefaultBg = Color(0xFFDCDAFC); + static const buttonSecondaryIcon = Color(0xFF2D25BC); + static const buttonSecondaryText = Color(0xFF2D25BC); + +// TopNav + + static const topNavIconPtimary = Color(0xFF787680); + static const topNavText = Color(0xFF000000); + static const topNavIconBack = Color(0xFF4A40EC); + static const topNavIconBg = Color(0xFFE3E0F9); + +// BottomNav + + static const bottomNavIconDefault = Color(0xFF787680); + static const bottomNavIconActive = Color(0xFF4A40EC); + static const bottomNavBackground = Color(0xFFFFFFFF); + static const bottomNavShadow = Color(0xFFF2EFFF); + +// 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); + +// TextField + + static const textFieldDefaultBg = Color(0xFFFCF8FF); + static const textFieldDefaultIconLead = Color(0xFFC9BFFF); + static const textFieldDefaultIconTrail = Color(0xFF5D55F2); + static const textFieldDefaultText = Color(0xFF837CA3); + + static const textFieldActiveBg = Color(0xFFFFFCFF); + static const textFieldActiveIconLead = Color(0xFF5D55F2); + static const textFieldActiveIconTrail = Color(0xFF5D55F2); + static const textFieldActiveText = Color(0xFF252229); + static const textFieldActiveLabel = Color(0xFF5D55F2); + + static const noteText = Color(0xFF787680); static const mainDarkBlue = Color.fromRGBO(3, 37, 65, 1); static const mainLightBlue = Color.fromRGBO(48, 86, 117, 1); + +// Post + + static const postFullName = Color(0xFF302F38); + static const postUserName = Color(0xFFC8C5D0); + static const postBg = Color(0xFFF9F8FF); + static const postTime = Color(0xFFC8C5D0); + static const postMoreIcon = Color(0xFF323232); + static const postBodyText = Color(0xFF000000); + static const postBodyLink = Color(0xFF3A00E5); + static const postActionNumber = Color(0xFFC8C5D0); + static const postActionIconDefault = Color(0xFFC8C5D0); + static const postActionIconPressed = Color(0xFFFE82B1); + static const postBookmark = Color(0xFF8482FF); + +// Profile + + static const profileWrapperBg = Color(0xFFF9F8FF); + static const profileFullName = Color(0xFF302F38); + static const profileUserName = Color(0xFF787680); + static const profileSummary = Color(0xFF302F38); + static const profileKeyField = Color(0xFFF2EFFF); + static const profileKeyText = Color(0xFF6E61A0); + static const profileSocialLinks = Color(0xFF4F46F1); + static const profileSocialIcons = Color(0xFF837CA3); + +// QR code + static const qrCodeBody = Color(0xFF25207A); } 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/lib/widgets/btn_continue.dart b/lib/widgets/btn_continue.dart new file mode 100644 index 0000000..1bda815 --- /dev/null +++ b/lib/widgets/btn_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/pubspec.lock b/pubspec.lock index 24ffa05..7592d65 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -352,11 +352,35 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.3" + qr: + dependency: transitive + description: + name: qr + sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" sky_engine: dependency: transitive 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 +429,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: @@ -463,4 +495,4 @@ packages: version: "6.2.2" sdks: dart: ">=2.19.6 <3.0.0" - flutter: ">=3.7.0-0" + flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index 68dea71..8fc49f1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,11 +34,13 @@ 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 another_flushbar: ^1.12.29 + qr_flutter: ^4.1.0 dev_dependencies: flutter_test: @@ -63,7 +65,10 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - assets/ + - assets/images/ + - assets/images/icons/ + - assets/images/logo/ + # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see