diff --git a/assets/translations/en.json b/assets/translations/en.json index 76165d9be2..058d277691 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -19,12 +19,14 @@ "connect": "Connect", "domain": "Domain", "saving": "Saving..", - "nickname": "nickname", + "nickname": "Nickname", "loading": "Loading...", "later": "I will setup it later", "reset": "Reset", "details": "Details", - "no_data": "No data" + "no_data": "No data", + "wait": "Wait", + "remove": "Remove" }, "more": { "_comment": "'More' tab", @@ -191,7 +193,6 @@ "23": "Enter a nickname and strong password", "finish": "Everything is initialized", "checks": "Checks have been completed \n{} ouf of {}" - }, "modals": { "_comment": "messages in modals", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 0fba1284e2..5fda5fca77 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -24,7 +24,9 @@ "later": "Настрою потом", "reset": "Reset", "details": "Детальная информация", - "no_data": "Нет данных" + "no_data": "Нет данных", + "wait": "Ожидайте", + "remove": "Удалить" }, "more": { "_comment": "вкладка еще", diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 40f32d303c..285337b4d7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -6,6 +6,8 @@ PODS: - Flutter - path_provider (0.0.1): - Flutter + - share_plus (0.0.1): + - Flutter - shared_preferences (0.0.1): - Flutter - url_launcher (0.0.1): @@ -18,6 +20,7 @@ DEPENDENCIES: - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - package_info (from `.symlinks/plugins/package_info/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) + - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - wakelock (from `.symlinks/plugins/wakelock/ios`) @@ -31,6 +34,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/package_info/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" + share_plus: + :path: ".symlinks/plugins/share_plus/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" url_launcher: @@ -43,6 +48,7 @@ SPEC CHECKSUMS: flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c + share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index ae7b2d81e9..5fe7138093 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -17,7 +17,7 @@ class BlocAndProviderConfig extends StatelessWidget { // SchedulerBinding.instance.window.platformBrightness; // var isDark = platformBrightness == Brightness.dark; var isDark = false; - + var usersCubit = UsersCubit(); return MultiProvider( providers: [ BlocProvider( @@ -31,8 +31,8 @@ class BlocAndProviderConfig extends StatelessWidget { create: (_) => AppConfigCubit()..load(), ), BlocProvider(create: (_) => ProvidersCubit()), - BlocProvider(create: (_) => UsersCubit()), - BlocProvider(create: (_) => JobsCubit()), + BlocProvider(create: (_) => usersCubit..load(), lazy: false), + BlocProvider(create: (_) => JobsCubit(usersCubit)), ], child: child, ); diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index eaf7b0eb0a..e704b50c3b 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -19,6 +19,8 @@ class HiveConfig { Hive.registerAdapter(HetznerDataBaseAdapter()); await Hive.openBox(BNames.appSettings); + await Hive.openBox(BNames.users); + var cipher = HiveAesCipher(await getEncriptedKey()); await Hive.openBox(BNames.appConfig, encryptionCipher: cipher); @@ -42,6 +44,7 @@ class BNames { static String appConfig = 'appConfig'; static String isDarkModeOn = 'isDarkModeOn'; static String isOnbordingShowing = 'isOnbordingShowing'; + static String users = 'users'; static String appSettings = 'appSettings'; diff --git a/lib/logic/api_maps/hetzner.dart b/lib/logic/api_maps/hetzner.dart index ad1eacb0ff..037db18cf3 100644 --- a/lib/logic/api_maps/hetzner.dart +++ b/lib/logic/api_maps/hetzner.dart @@ -95,7 +95,7 @@ class HetznerApi extends ApiMap { var dbId = dbCreateResponse.data['volume']['id']; var data = jsonDecode( - '''{"name":"$domainName","server_type":"cx11","start_after_create":false,"image":"ubuntu-20.04", "volumes":[$dbId], "networks":[],"user_data":"#cloud-config\\nruncmd:\\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-20.09 DOMAIN=$domainName LUSER=${rootUser.login} PASSWORD=${rootUser.password} HASHED_PASSWORD=${rootUser.hashPassword.hash} SALT=${rootUser.hashPassword.salt} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":true, "location": "fsn1"}''', + '''{"name":"$domainName","server_type":"cx11","start_after_create":false,"image":"ubuntu-20.04", "volumes":[$dbId], "networks":[], "user_data":"#cloud-config\\nruncmd:\\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-21.05 DOMAIN=$domainName LUSER=${rootUser.login} PASSWORD=${rootUser.password} HASHED_PASSWORD=${rootUser.hashPassword.hash} SALT=${rootUser.hashPassword.salt} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":true, "location": "fsn1"}''' ); Response serverCreateResponse = await client.post( diff --git a/lib/logic/api_maps/server.dart b/lib/logic/api_maps/server.dart index 071680063c..aae495389d 100644 --- a/lib/logic/api_maps/server.dart +++ b/lib/logic/api_maps/server.dart @@ -51,8 +51,9 @@ class ServerApi extends ApiMap { '/createUser', options: Options( headers: { - "X-Username": user.login, - "X-Password": user.password, + "X-User": user.login, + "X-Password": + '\$6\$${user.hashPassword.salt}\$${user.hashPassword.hash}', }, ), ); @@ -68,4 +69,24 @@ class ServerApi extends ApiMap { String get rootAddress => throw UnimplementedError('not used in with implementation'); + + Future apply() async { + bool res; + Response response; + + var client = await getClient(); + try { + response = await client.get( + '/apply', + ); + + res = response.statusCode == HttpStatus.ok; + } catch (e) { + print(e); + res = false; + } + + close(client); + return res; + } } diff --git a/lib/logic/cubit/jobs/jobs_cubit.dart b/lib/logic/cubit/jobs/jobs_cubit.dart index ee4b9ed056..aebe2ddd47 100644 --- a/lib/logic/cubit/jobs/jobs_cubit.dart +++ b/lib/logic/cubit/jobs/jobs_cubit.dart @@ -1,32 +1,53 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/server.dart'; +import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; import 'package:selfprivacy/logic/models/jobs/job.dart'; import 'package:equatable/equatable.dart'; +import 'package:selfprivacy/logic/models/user.dart'; export 'package:provider/provider.dart'; part 'jobs_state.dart'; class JobsCubit extends Cubit { - JobsCubit() : super(JobsState.emtpy()); + JobsCubit(this.usersCubit) : super(JobsStateEmpty()); final api = ServerApi(); + final UsersCubit usersCubit; void addJob(Job job) { - final newState = state.addJob(job); - emit(newState); + var newJobsList = []; + if (state is JobsStateWithJobs) { + newJobsList.addAll((state as JobsStateWithJobs).jobList); + } + newJobsList.add(job); + emit(JobsStateWithJobs(newJobsList)); } void removeJob(String id) { - final newState = state.removeById(id); + final newState = (state as JobsStateWithJobs).removeById(id); emit(newState); } Future applyAll() async { - for (var job in state.jobList) { - if (job is CreateUserJob) { - // await api.createUser(job.user); + if (state is JobsStateWithJobs) { + var jobs = (state as JobsStateWithJobs).jobList; + emit(JobsStateLoading()); + + var newUsers = []; + for (var job in jobs) { + if (job is CreateUserJob) { + newUsers.add(job.user); + await api.createUser(job.user); + } } + + usersCubit.addUsers(newUsers); + await api.apply(); + + emit(JobsStateEmpty()); + + getIt().navigator!.pop(); } - emit(JobsState.emtpy()); } } diff --git a/lib/logic/cubit/jobs/jobs_state.dart b/lib/logic/cubit/jobs/jobs_state.dart index e761b8170a..972f4b3dd7 100644 --- a/lib/logic/cubit/jobs/jobs_state.dart +++ b/lib/logic/cubit/jobs/jobs_state.dart @@ -1,25 +1,27 @@ part of 'jobs_cubit.dart'; -class JobsState extends Equatable { - const JobsState(this.jobList); +abstract class JobsState extends Equatable { + @override + List get props => []; +} +class JobsStateLoading extends JobsState {} + +class JobsStateEmpty extends JobsState {} + +class JobsStateWithJobs extends JobsState { + JobsStateWithJobs(this.jobList); final List jobList; - static JobsState emtpy() => JobsState([]); - - bool get isEmpty => jobList.isEmpty; - - JobsState addJob(Job job) { - var newJobsList = [...jobList]; - newJobsList.add(job); - return JobsState(newJobsList); - } - JobsState removeById(String id) { var newJobsList = jobList.where((element) => element.id != id).toList(); - return JobsState(newJobsList); + + if (newJobsList.isEmpty) { + return JobsStateEmpty(); + } + return JobsStateWithJobs(newJobsList); } @override - List get props => jobList; + List get props => jobList; } diff --git a/lib/logic/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart index 6e00f7e9da..ecc0c89767 100644 --- a/lib/logic/cubit/users/users_cubit.dart +++ b/lib/logic/cubit/users/users_cubit.dart @@ -1,23 +1,35 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:hive/hive.dart'; +import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/logic/models/user.dart'; export 'package:provider/provider.dart'; part 'users_state.dart'; class UsersCubit extends Cubit { - UsersCubit() : super(UsersState([])); + UsersCubit() : super(UsersState([])); + Box box = Hive.box(BNames.users); - void addUser(User user) { - var users = [...state.users]; - users.add(user); - - emit(UsersState(users)); + void load() async { + var loadedUsers = box.values.toList(); + if (loadedUsers.isNotEmpty) { + emit(UsersState(loadedUsers)); + } } - void remove(User? user) { + void addUsers(List users) async { + var newUserList = [...state.users, ...users]; + + await box.addAll(users); + emit(UsersState(newUserList)); + } + + void remove(User user) async { var users = [...state.users]; + var index = users.indexOf(user); users.remove(user); + await box.deleteAt(index); emit(UsersState(users)); } diff --git a/lib/main.dart b/lib/main.dart index 231a7dca28..67199de055 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -23,40 +23,41 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); - runApp( - Localization( - child: BlocAndProviderConfig( - child: MyApp(), - ), - ), - ); + runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - AppSettingsState appSettings = context.watch().state; + return Localization( + child: BlocAndProviderConfig( + child: Builder(builder: (context) { + var appSettings = context.watch().state; - return AnnotatedRegion( - value: SystemUiOverlayStyle.light, // Manually changnig appbar color - child: MaterialApp( - navigatorKey: getIt.get().navigatorKey, - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, - title: 'SelfPrivacy', - theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme, - home: appSettings.isOnbordingShowing - ? OnboardingPage(nextPage: InitializingPage()) - : RootPage(), - builder: (BuildContext context, Widget? widget) { - Widget error = Text('...rendering error...'); - if (widget is Scaffold || widget is Navigator) - error = Scaffold(body: Center(child: error)); - ErrorWidget.builder = (FlutterErrorDetails errorDetails) => error; - return widget!; - }, + return AnnotatedRegion( + value: SystemUiOverlayStyle.light, // Manually changnig appbar color + child: MaterialApp( + navigatorKey: getIt.get().navigatorKey, + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, + title: 'SelfPrivacy', + theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme, + home: appSettings.isOnbordingShowing + ? OnboardingPage(nextPage: InitializingPage()) + : RootPage(), + builder: (BuildContext context, Widget? widget) { + Widget error = Text('...rendering error...'); + if (widget is Scaffold || widget is Navigator) + error = Scaffold(body: Center(child: error)); + ErrorWidget.builder = + (FlutterErrorDetails errorDetails) => error; + return widget!; + }, + ), + ); + }), ), ); } diff --git a/lib/ui/components/brand_loader/brand_loader.dart b/lib/ui/components/brand_loader/brand_loader.dart new file mode 100644 index 0000000000..52b1b820d8 --- /dev/null +++ b/lib/ui/components/brand_loader/brand_loader.dart @@ -0,0 +1,21 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; + +class BrandLoader { + static horizontal() => _HorizontalLoader(); +} + +class _HorizontalLoader extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('basis.wait'.tr()), + SizedBox(height: 10), + LinearProgressIndicator(minHeight: 3), + ], + ); + } +} diff --git a/lib/ui/components/brand_text/brand_text.dart b/lib/ui/components/brand_text/brand_text.dart index 1acbb4e8ba..9f272782e2 100644 --- a/lib/ui/components/brand_text/brand_text.dart +++ b/lib/ui/components/brand_text/brand_text.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/text_themes.dart'; +export 'package:selfprivacy/utils/extensions/text_extensions.dart'; enum TextType { h1, // right now only at onboarding and opened providers diff --git a/lib/ui/components/jobs_content/jobs_content.dart b/lib/ui/components/jobs_content/jobs_content.dart index e815826f62..aa4b1c6555 100644 --- a/lib/ui/components/jobs_content/jobs_content.dart +++ b/lib/ui/components/jobs_content/jobs_content.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart'; +import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; class JobsContent extends StatelessWidget { @@ -12,55 +15,71 @@ class JobsContent extends StatelessWidget { @override Widget build(BuildContext context) { - var jobs = context.watch().state; - return ListView( - padding: paddingH15V0, - children: [ - SizedBox(height: 15), - Center( - child: BrandText.h2( - 'jobs.title'.tr(), - ), - ), - SizedBox(height: 20), - if (jobs.isEmpty) BrandText.body1('jobs.empty'.tr()), - if (!jobs.isEmpty) ...[ - ...jobs.jobList - .map( - (j) => Row( - children: [ - Expanded( - child: BrandCards.small( - child: Row( - children: [ - BrandText.body1(j.title), - ], + return BlocBuilder( + builder: (context, state) { + late final List widgets; + if (state is JobsStateEmpty) { + widgets = [ + SizedBox(height: 80), + Center(child: BrandText.body1('jobs.empty'.tr())), + ]; + } else if (state is JobsStateLoading) { + widgets = [ + SizedBox(height: 80), + BrandLoader.horizontal(), + ]; + } else if (state is JobsStateWithJobs) { + widgets = [ + ...state.jobList + .map( + (j) => Row( + children: [ + Expanded( + child: BrandCards.small( + child: Row( + children: [ + BrandText.body1(j.title), + ], + ), ), ), - ), - SizedBox(width: 10), - ElevatedButton( - style: ElevatedButton.styleFrom( - primary: BrandColors.red1, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + SizedBox(width: 10), + ElevatedButton( + style: ElevatedButton.styleFrom( + primary: BrandColors.red1, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), ), + onPressed: () => + context.read().removeJob(j.id), + child: Text('basis.remove'.tr()), ), - onPressed: () => - context.read().removeJob(j.id), - child: Text('Remove'), - ), - ], - ), - ) - .toList(), - SizedBox(height: 20), - BrandButton.rised( - onPressed: () => context.read().applyAll(), - text: 'jobs.start'.tr(), - ), - ], - ], + ], + ), + ) + .toList(), + SizedBox(height: 20), + BrandButton.rised( + onPressed: () => context.read().applyAll(), + text: 'jobs.start'.tr(), + ), + ]; + } + return ListView( + padding: paddingH15V0, + children: [ + SizedBox(height: 15), + Center( + child: BrandText.h2( + 'jobs.title'.tr(), + ), + ), + SizedBox(height: 20), + ...widgets + ], + ); + }, ); } } diff --git a/lib/ui/components/pre_styled_buttons/flash.dart b/lib/ui/components/pre_styled_buttons/flash.dart index 30151a56e5..5e9b187552 100644 --- a/lib/ui/components/pre_styled_buttons/flash.dart +++ b/lib/ui/components/pre_styled_buttons/flash.dart @@ -42,20 +42,18 @@ class _BrandFlashButtonState extends State<_BrandFlashButton> super.dispose(); } - late bool wasPrevStateIsEmpty; + bool wasPrevStateIsEmpty = true; @override Widget build(BuildContext context) { - var hasNoJobs = context.watch().state.isEmpty; - wasPrevStateIsEmpty = hasNoJobs; - var icon = hasNoJobs ? Ionicons.flash_outline : Ionicons.flash; - return BlocListener( listener: (context, state) { - if (wasPrevStateIsEmpty && state.jobList.isNotEmpty) { + if (wasPrevStateIsEmpty && state is! JobsStateEmpty) { wasPrevStateIsEmpty = false; _animationController.forward(); - } else if (!wasPrevStateIsEmpty && state.jobList.isEmpty) { + } else if (!wasPrevStateIsEmpty && state is JobsStateEmpty) { + wasPrevStateIsEmpty = true; + _animationController.reverse(); } }, @@ -73,6 +71,7 @@ class _BrandFlashButtonState extends State<_BrandFlashButton> animation: _colorTween, builder: (context, child) { var v = _animationController.value; + var icon = v > 0.5 ? Ionicons.flash : Ionicons.flash_outline; return Transform.scale( scale: 1 + (v < 0.5 ? v : 1 - v) * 2, child: Icon( diff --git a/lib/ui/pages/server_details/cpu_chart.dart b/lib/ui/pages/server_details/cpu_chart.dart index da1283d460..88163caaf8 100644 --- a/lib/ui/pages/server_details/cpu_chart.dart +++ b/lib/ui/pages/server_details/cpu_chart.dart @@ -82,7 +82,6 @@ class CpuChart extends StatelessWidget { double appliedInterval, double value, ) { - print(value); if (value < 0) { return false; } else if (value == 0) { diff --git a/lib/ui/pages/users/user.dart b/lib/ui/pages/users/user.dart index 5be15912dd..844082b75a 100644 --- a/lib/ui/pages/users/user.dart +++ b/lib/ui/pages/users/user.dart @@ -1,9 +1,9 @@ part of 'users.dart'; class _User extends StatelessWidget { - const _User({Key? key, this.user}) : super(key: key); + const _User({Key? key, required this.user}) : super(key: key); - final User? user; + final User user; @override Widget build(BuildContext context) { return InkWell( @@ -24,12 +24,12 @@ class _User extends StatelessWidget { width: 17, height: 17, decoration: BoxDecoration( - color: user!.color, + color: user.color, shape: BoxShape.circle, ), ), SizedBox(width: 20), - BrandText.h4(user!.login), + BrandText.h4(user.login), ], ), ), diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index eae8217ce6..8faf5a83af 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -3,10 +3,10 @@ part of 'users.dart'; class _UserDetails extends StatelessWidget { const _UserDetails({ Key? key, - this.user, + required this.user, }) : super(key: key); - final User? user; + final User user; @override Widget build(BuildContext context) { @@ -23,7 +23,7 @@ class _UserDetails extends StatelessWidget { Container( height: 200, decoration: BoxDecoration( - color: user!.color, + color: user.color, borderRadius: BorderRadius.vertical( top: Radius.circular(20), ), @@ -116,7 +116,7 @@ class _UserDetails extends StatelessWidget { horizontal: 15, ), child: BrandText.h1( - user!.login, + user.login, softWrap: true, overflow: TextOverflow.ellipsis, )), @@ -133,14 +133,14 @@ class _UserDetails extends StatelessWidget { Container( height: 40, alignment: Alignment.centerLeft, - child: BrandText.h4('${user!.login}@$domainName'), + child: BrandText.h4('${user.login}@$domainName'), ), SizedBox(height: 14), BrandText.small('basis.password'.tr()), Container( height: 40, alignment: Alignment.centerLeft, - child: BrandText.h4(user!.password), + child: BrandText.h4(user.password), ), SizedBox(height: 24), BrandDivider(), @@ -148,7 +148,10 @@ class _UserDetails extends StatelessWidget { BrandButton.emptyWithIconText( title: 'users.send_regisration_data'.tr(), icon: Icon(BrandIcons.share), - onPressed: () {}, + onPressed: () { + Share.share( + 'login: ${user.login}, password: ${user.password}'); + }, ), SizedBox(height: 20), ], diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index b2cb4e67ef..46d0b1845e 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -17,6 +17,7 @@ import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; import 'package:selfprivacy/utils/ui_helpers.dart'; +import 'package:share_plus/share_plus.dart'; part 'fab.dart'; part 'new_user.dart'; @@ -33,7 +34,6 @@ class UsersPage extends StatelessWidget { var isReady = context.watch().state.isFullyInitilized; final users = usersCubitState.users; final isEmpty = usersCubitState.isEmpty; - Widget child; if (!isReady) { @@ -48,7 +48,7 @@ class UsersPage extends StatelessWidget { ) : ListView( children: [ - ...users.map((user) => _User(user: user)), + ...users.map((user) => _User(user: user)).toList(), ], ); } diff --git a/lib/utils/extensions/text_extensions.dart b/lib/utils/extensions/text_extensions.dart new file mode 100644 index 0000000000..7e378d0cfe --- /dev/null +++ b/lib/utils/extensions/text_extensions.dart @@ -0,0 +1,51 @@ +import 'dart:ui'; +import 'package:flutter/cupertino.dart'; + +extension TextExtension on Text { + Text withColor(Color color) => Text( + data!, + key: this.key, + strutStyle: this.strutStyle, + textAlign: this.textAlign, + textDirection: this.textDirection, + locale: this.locale, + softWrap: this.softWrap, + overflow: this.overflow, + textScaleFactor: this.textScaleFactor, + maxLines: this.maxLines, + semanticsLabel: this.semanticsLabel, + textWidthBasis: textWidthBasis ?? this.textWidthBasis, + style: this.style != null + ? this.style!.copyWith(color: color) + : TextStyle(color: color), + ); + + Text copyWith({ + Key? key, + StrutStyle? strutStyle, + TextAlign? textAlign, + TextDirection? textDirection, + Locale? locale, + bool? softWrap, + TextOverflow? overflow, + double? textScaleFactor, + int? maxLines, + String? semanticsLabel, + TextWidthBasis? textWidthBasis, + TextStyle? style, + }) { + return Text(data!, + key: key ?? this.key, + strutStyle: strutStyle ?? this.strutStyle, + textAlign: textAlign ?? this.textAlign, + textDirection: textDirection ?? this.textDirection, + locale: locale ?? this.locale, + softWrap: softWrap ?? this.softWrap, + overflow: overflow ?? this.overflow, + textScaleFactor: textScaleFactor ?? this.textScaleFactor, + maxLines: maxLines ?? this.maxLines, + semanticsLabel: semanticsLabel ?? this.semanticsLabel, + textWidthBasis: textWidthBasis ?? this.textWidthBasis, + style: style != null ? this.style?.merge(style) ?? style : this.style); + } +} diff --git a/pubspec.lock b/pubspec.lock index a8ac47f158..dca1a9511c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -651,6 +651,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + share_plus_linux: + dependency: transitive + description: + name: share_plus_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + share_plus_macos: + dependency: transitive + description: + name: share_plus_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + share_plus_web: + dependency: transitive + description: + name: share_plus_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + share_plus_windows: + dependency: transitive + description: + name: share_plus_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" shared_preferences: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2ae1c63d94..d8a735891c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: package_info: ^2.0.0 pretty_dio_logger: ^1.1.1 provider: ^5.0.0 + share_plus: ^2.1.4 unicons: ^1.0.2 url_launcher: ^6.0.2 wakelock: ^0.5.0+2