Merge branch 'master' into backblaze-refactor

This commit is contained in:
NaiJi 2023-09-26 15:14:59 -03:00
commit 08988ed10b
10 changed files with 253 additions and 137 deletions

View file

@ -0,0 +1,68 @@
name: Bug report
about: File a bug report
labels:
- Bug
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please provide a short but a descriptive title for your issue.
- type: textarea
id: expected-behaviour
attributes:
label: Expected Behavior
description: What did you expect to happen?
validations:
required: true
- type: textarea
id: actual-behaviour
attributes:
label: Actual Behavior
description: What actually happened?
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to Reproduce
description: What steps can we follow to reproduce this issue?
placeholder: |
1. First step
2. Second step
3. and so on...
validations:
required: true
- type: textarea
id: context
attributes:
label: Context and notes
description: Additional information about environment or what were you trying to do. If you have an idea how to fix this issue, please describe it here too.
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output, if you have any. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: input
id: app-version
attributes:
label: App Version
description: What version of SelfPrivacy app are you running? You can find it in the "About" section of the app.
validations:
required: true
- type: input
id: api-version
attributes:
label: Server API Version
description: What version of SelfPrivacy API are you running? You can find it in the "About" section of the app. Leave it empty if your app is not connected to the server yet.
- type: dropdown
id: os
attributes:
label: Operating System
description: What operating system are you using?
options:
- Android
- iOS
- Linux
- macOS
- Windows

View file

@ -0,0 +1,23 @@
name: Feature request
about: Suggest an idea for this project
label:
- Feature request
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request! Please provide a short but a descriptive title for your issue.
- type: textarea
id: description
attributes:
label: Description
description: Describe the feature you'd like to see.
placeholder: |
As a user, I want to be able to...
validations:
required: true
- type: textarea
id: context
attributes:
label: Context and notes
description: Additional information about environment and what were you trying to do. If you have an idea how to implement this feature, please describe it here too.

View file

@ -0,0 +1,29 @@
name: Translation issue
about: File a translation (localization) issue
labels:
- Translations
body:
- type: markdown
attributes:
value: |
Translations can be modified and discussed on [Weblate](https://weblate.selfprivacy.org/projects/selfprivacy/). You can fix the mistranslation issue yourself there. Using the search, you can also find the string ID of the mistranslated string. If your issue is more complex, please file it here
If you are a member of SelfPrivacy core team, you **must** fix the issue yourself on Weblate.
- type: input
id: language
attributes:
label: Language
description: What language is affected?
placeholder: |
English
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: Describe the issue in detail. If you have an idea how to fix this issue, please describe it here too. Include the string ID of the mistranslated string, if possible.
placeholder: |
The string `string.id` is translated as "foo", but it should be "bar".
validations:
required: true

View file

@ -35,7 +35,8 @@
"done": "Done", "done": "Done",
"continue": "Continue", "continue": "Continue",
"alert": "Alert", "alert": "Alert",
"copied_to_clipboard": "Copied to clipboard!" "copied_to_clipboard": "Copied to clipboard!",
"please_connect": "Please connect your server, domain and DNS provider to dive in!"
}, },
"more_page": { "more_page": {
"configuration_wizard": "Setup wizard", "configuration_wizard": "Setup wizard",
@ -315,6 +316,7 @@
"in_menu": "Server is not set up yet. Please finish setup using setup wizard for further work." "in_menu": "Server is not set up yet. Please finish setup using setup wizard for further work."
}, },
"service_page": { "service_page": {
"nothing_here": "Nothing here",
"open_in_browser": "Open in browser", "open_in_browser": "Open in browser",
"restart": "Restart service", "restart": "Restart service",
"disable": "Disable service", "disable": "Disable service",
@ -371,7 +373,6 @@
"add_new_user": "Add a first user", "add_new_user": "Add a first user",
"new_user": "New user", "new_user": "New user",
"delete_user": "Delete user", "delete_user": "Delete user",
"not_ready": "Please connect server, domain and DNS in the Providers tab, to be able to add a first user",
"nobody_here": "Nobody here", "nobody_here": "Nobody here",
"login": "Login", "login": "Login",
"new_user_info_note": "New user will automatically be granted an access to all of the services", "new_user_info_note": "New user will automatically be granted an access to all of the services",

View file

@ -75,7 +75,7 @@ class DnsRecordsCubit
@override @override
Future<void> clear() async { Future<void> clear() async {
emit(const DnsRecordsState(dnsState: DnsRecordsStatus.error)); emit(const DnsRecordsState(dnsState: DnsRecordsStatus.uninitialized));
} }
Future<void> refresh() async { Future<void> refresh() async {

View file

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
class EmptyPagePlaceholder extends StatelessWidget {
const EmptyPagePlaceholder({
required this.title,
required this.iconData,
required this.description,
this.showReadyCard = false,
super.key,
});
final String title;
final String description;
final IconData iconData;
final bool showReadyCard;
@override
Widget build(final BuildContext context) => !showReadyCard
? _expandedContent(context)
: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: NotReadyCard(),
),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Center(
child: _expandedContent(context),
),
),
)
],
);
Widget _expandedContent(final BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
iconData,
size: 50,
color: Theme.of(context).colorScheme.onBackground,
),
const SizedBox(height: 16),
Text(
title,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
),
const SizedBox(height: 8),
Text(
description,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
),
],
),
);
}

View file

@ -49,6 +49,8 @@ class _ProvidersPageState extends State<ProvidersPage> {
return StateType.stable; return StateType.stable;
} }
bool isClickable() => getServerStatus() != StateType.uninitialized;
StateType getDnsStatus() { StateType getDnsStatus() {
if (dnsStatus == DnsRecordsStatus.uninitialized || if (dnsStatus == DnsRecordsStatus.uninitialized ||
dnsStatus == DnsRecordsStatus.refreshing) { dnsStatus == DnsRecordsStatus.refreshing) {
@ -83,7 +85,9 @@ class _ProvidersPageState extends State<ProvidersPage> {
subtitle: diskStatus.isDiskOkay subtitle: diskStatus.isDiskOkay
? 'storage.status_ok'.tr() ? 'storage.status_ok'.tr()
: 'storage.status_error'.tr(), : 'storage.status_error'.tr(),
onTap: () => context.pushRoute(const ServerDetailsRoute()), onTap: isClickable()
? () => context.pushRoute(const ServerDetailsRoute())
: null,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_Card( _Card(
@ -93,7 +97,9 @@ class _ProvidersPageState extends State<ProvidersPage> {
subtitle: appConfig.isDomainSelected subtitle: appConfig.isDomainSelected
? appConfig.serverDomain!.domainName ? appConfig.serverDomain!.domainName
: '', : '',
onTap: () => context.pushRoute(const DnsDetailsRoute()), onTap: isClickable()
? () => context.pushRoute(const DnsDetailsRoute())
: null,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_Card( _Card(
@ -103,7 +109,9 @@ class _ProvidersPageState extends State<ProvidersPage> {
icon: BrandIcons.save, icon: BrandIcons.save,
title: 'backup.card_title'.tr(), title: 'backup.card_title'.tr(),
subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '', subtitle: isBackupInitialized ? 'backup.card_subtitle'.tr() : '',
onTap: () => context.pushRoute(const BackupDetailsRoute()), onTap: isClickable()
? () => context.pushRoute(const BackupDetailsRoute())
: null,
), ),
], ],
), ),

View file

@ -7,9 +7,10 @@ import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/ui/helpers/empty_page_placeholder.dart';
import 'package:selfprivacy/ui/router/router.dart'; import 'package:selfprivacy/ui/router/router.dart';
import 'package:selfprivacy/utils/breakpoints.dart'; import 'package:selfprivacy/utils/breakpoints.dart';
import 'package:selfprivacy/utils/launch_url.dart'; import 'package:selfprivacy/utils/launch_url.dart';
@ -42,7 +43,14 @@ class _ServicesPageState extends State<ServicesPage> {
), ),
) )
: null, : null,
body: RefreshIndicator( body: !isReady
? EmptyPagePlaceholder(
showReadyCard: true,
title: 'service_page.nothing_here'.tr(),
description: 'basis.please_connect'.tr(),
iconData: BrandIcons.box,
)
: RefreshIndicator(
onRefresh: () async { onRefresh: () async {
await context.read<ServicesCubit>().reload(); await context.read<ServicesCubit>().reload();
}, },
@ -54,7 +62,6 @@ class _ServicesPageState extends State<ServicesPage> {
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
if (!isReady) ...[const NotReadyCard(), const SizedBox(height: 24)],
...services.map( ...services.map(
(final service) => Padding( (final service) => Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@ -129,16 +136,16 @@ class _Card extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(width: 8),
Text(
service.displayName,
style: Theme.of(context).textTheme.headlineMedium,
),
], ],
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 12),
Text(
service.displayName,
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 8), const SizedBox(height: 8),
if (service.url != '' && service.url != null) if (service.url != '' && service.url != null)
Column( Column(
@ -146,7 +153,7 @@ class _Card extends StatelessWidget {
_ServiceLink( _ServiceLink(
url: service.url ?? '', url: service.url ?? '',
), ),
const SizedBox(height: 10), const SizedBox(height: 8),
], ],
), ),
if (service.id == 'mailserver') if (service.id == 'mailserver')
@ -156,21 +163,21 @@ class _Card extends StatelessWidget {
url: domainName, url: domainName,
isActive: false, isActive: false,
), ),
const SizedBox(height: 10), const SizedBox(height: 8),
], ],
), ),
Text( Text(
service.description, service.description,
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
), ),
const SizedBox(height: 10), const SizedBox(height: 8),
Text( Text(
service.loginInfo, service.loginInfo,
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.secondary, color: Theme.of(context).colorScheme.secondary,
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 8),
], ],
) )
], ],

View file

@ -1,73 +0,0 @@
part of 'users.dart';
class _NoUsers extends StatelessWidget {
const _NoUsers({required this.text});
final String text;
@override
Widget build(final BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
BrandIcons.users,
size: 50,
color: Theme.of(context).colorScheme.onBackground,
),
const SizedBox(height: 20),
Text(
'users.nobody_here'.tr(),
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
),
const SizedBox(height: 10),
Text(
text,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
),
],
),
);
}
class _CouldNotLoadUsers extends StatelessWidget {
const _CouldNotLoadUsers({required this.text});
final String text;
@override
Widget build(final BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
BrandIcons.users,
size: 50,
color: Theme.of(context).colorScheme.onBackground,
),
const SizedBox(height: 20),
Text(
'users.could_not_fetch_users'.tr(),
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
),
const SizedBox(height: 10),
Text(
text,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
),
],
),
);
}

View file

@ -16,17 +16,16 @@ import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
import 'package:selfprivacy/ui/components/buttons/outlined_button.dart'; import 'package:selfprivacy/ui/components/buttons/outlined_button.dart';
import 'package:selfprivacy/ui/components/cards/filled_card.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/helpers/empty_page_placeholder.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/info_box/info_box.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart';
import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart'; import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package:selfprivacy/ui/router/router.dart'; import 'package:selfprivacy/ui/router/router.dart';
import 'package:selfprivacy/utils/breakpoints.dart'; import 'package:selfprivacy/utils/breakpoints.dart';
import 'package:selfprivacy/utils/platform_adapter.dart'; import 'package:selfprivacy/utils/platform_adapter.dart';
import 'package:selfprivacy/utils/ui_helpers.dart'; import 'package:selfprivacy/utils/ui_helpers.dart';
part 'empty.dart';
part 'new_user.dart'; part 'new_user.dart';
part 'user.dart'; part 'user.dart';
part 'user_details.dart'; part 'user_details.dart';
@ -43,7 +42,12 @@ class UsersPage extends StatelessWidget {
Widget child; Widget child;
if (!isReady) { if (!isReady) {
child = isNotReady(); child = EmptyPagePlaceholder(
showReadyCard: true,
title: 'users.nobody_here'.tr(),
description: 'basis.please_connect'.tr(),
iconData: BrandIcons.users,
);
} else { } else {
child = BlocBuilder<UsersCubit, UsersState>( child = BlocBuilder<UsersCubit, UsersState>(
builder: (final BuildContext context, final UsersState state) { builder: (final BuildContext context, final UsersState state) {
@ -64,8 +68,10 @@ class UsersPage extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
_CouldNotLoadUsers( EmptyPagePlaceholder(
text: 'users.could_not_fetch_description'.tr(), title: 'users.could_not_fetch_users'.tr(),
description: 'users.could_not_fetch_description'.tr(),
iconData: BrandIcons.users,
), ),
const SizedBox(height: 18), const SizedBox(height: 18),
BrandOutlinedButton( BrandOutlinedButton(
@ -132,24 +138,4 @@ class UsersPage extends StatelessWidget {
body: child, body: child,
); );
} }
Widget isNotReady() => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: NotReadyCard(),
),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Center(
child: _NoUsers(
text: 'users.not_ready'.tr(),
),
),
),
)
],
);
} }