mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-24 23:32:13 +00:00
improving onboarding flow and start with security profiles
Some checks failed
Flutter analyze & test / flutter_analyze_and_test (push) Has been cancelled
Some checks failed
Flutter analyze & test / flutter_analyze_and_test (push) Has been cancelled
This commit is contained in:
parent
f42a49cadf
commit
b7c4832ee2
36 changed files with 966 additions and 184 deletions
|
|
@ -3,6 +3,7 @@
|
|||
## 0.2.17
|
||||
|
||||
- New: Adds an "Ask a Friend" button to new contact suggestions.
|
||||
- New: Adds security profiles.
|
||||
- Improved: Onboarding flow for new users.
|
||||
- Improved: The blue verification checkmark now displays the total number of verifications.
|
||||
- Fix: Issue with receiving messages when user closed app while decrypting
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
|
@ -15,7 +16,8 @@ class LoggingCallbacks {
|
|||
Log.info(log.split('INFO ')[1]);
|
||||
} else if (log.contains('DEBUG ')) {
|
||||
Log.info(log.split('DEBUG ')[1]);
|
||||
} else if (kDebugMode) {
|
||||
} else if (kDebugMode && !Platform.environment.containsKey('FLUTTER_TEST')) {
|
||||
// ignore: avoid_print
|
||||
print(log);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ class Routes {
|
|||
'/settings/privacy/block_users';
|
||||
static const String settingsPrivacyUserDiscovery =
|
||||
'/settings/privacy/user_discovery';
|
||||
static const String settingsPrivacyProfileSelection =
|
||||
'/settings/privacy/profile_selection';
|
||||
static const String settingsNotification = '/settings/notification';
|
||||
static const String settingsStorage = '/settings/storage_data';
|
||||
static const String settingsStorageManage = '/settings/storage_data/manage';
|
||||
|
|
|
|||
|
|
@ -626,6 +626,54 @@ abstract class AppLocalizations {
|
|||
/// **'{len} contact(s)'**
|
||||
String settingsPrivacyBlockUsersCount(Object len);
|
||||
|
||||
/// No description provided for @settingsPrivacyProfileSelectionTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Security Profile'**
|
||||
String get settingsPrivacyProfileSelectionTitle;
|
||||
|
||||
/// No description provided for @settingsPrivacyProfileSelectionDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose your setup path and security configuration'**
|
||||
String get settingsPrivacyProfileSelectionDesc;
|
||||
|
||||
/// No description provided for @securityProfileTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Security Profile'**
|
||||
String get securityProfileTitle;
|
||||
|
||||
/// No description provided for @securityProfileSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose the level of protection that fits your daily use. This can be changed at any time in your settings.'**
|
||||
String get securityProfileSubtitle;
|
||||
|
||||
/// No description provided for @securityProfileNormalTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Normal Protection'**
|
||||
String get securityProfileNormalTitle;
|
||||
|
||||
/// No description provided for @securityProfileNormalDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Good balance between a convenient mode without bothering you too much.'**
|
||||
String get securityProfileNormalDesc;
|
||||
|
||||
/// No description provided for @securityProfileStrictTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Strict Protection'**
|
||||
String get securityProfileStrictTitle;
|
||||
|
||||
/// No description provided for @securityProfileStrictDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Maximum anti-phishing protection but may be inconvenient.'**
|
||||
String get securityProfileStrictDesc;
|
||||
|
||||
/// No description provided for @settingsNotification.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -2441,7 +2489,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @userDiscoverySettingsManualApproval.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask before sharing'**
|
||||
/// **'Ask every time before sharing'**
|
||||
String get userDiscoverySettingsManualApproval;
|
||||
|
||||
/// No description provided for @userDiscoverySettingsManualApprovalDesc.
|
||||
|
|
@ -3355,6 +3403,60 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Show username'**
|
||||
String get showUsername;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose your setup path'**
|
||||
String get onboardingProfileSelectionTitle;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose how you want to configure your security and privacy settings.'**
|
||||
String get onboardingProfileSelectionSubtitle;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionDefaultTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Default'**
|
||||
String get onboardingProfileSelectionDefaultTitle;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionDefaultDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Instantly applies recommended settings so you can start using the app.'**
|
||||
String get onboardingProfileSelectionDefaultDesc;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionDefaultBadge.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Fast Setup'**
|
||||
String get onboardingProfileSelectionDefaultBadge;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionCustomizeTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Customize'**
|
||||
String get onboardingProfileSelectionCustomizeTitle;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionCustomizeDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Step-by-step setup so you can decide for yourself.'**
|
||||
String get onboardingProfileSelectionCustomizeDesc;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionStrictTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enhanced Protection'**
|
||||
String get onboardingProfileSelectionStrictTitle;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionStrictDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Maximum anti-phishing defense. Recommended for *journalists & public figures*.'**
|
||||
String get onboardingProfileSelectionStrictDesc;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -292,6 +292,34 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
return '$len Kontakt(e)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsPrivacyProfileSelectionTitle => 'Sicherheitsprofil';
|
||||
|
||||
@override
|
||||
String get settingsPrivacyProfileSelectionDesc =>
|
||||
'Wähle deinen Setup-Pfad und deine Sicherheitskonfiguration';
|
||||
|
||||
@override
|
||||
String get securityProfileTitle => 'Sicherheitsprofil';
|
||||
|
||||
@override
|
||||
String get securityProfileSubtitle =>
|
||||
'Wähle das Schutzniveau, das zu deiner täglichen Nutzung passt. Dies kann jederzeit in den Einstellungen geändert werden.';
|
||||
|
||||
@override
|
||||
String get securityProfileNormalTitle => 'Normaler Schutz';
|
||||
|
||||
@override
|
||||
String get securityProfileNormalDesc =>
|
||||
'Gute Balance zwischen Komfort und Sicherheit, ohne dich zu sehr einzuschränken.';
|
||||
|
||||
@override
|
||||
String get securityProfileStrictTitle => 'Strikter Schutz';
|
||||
|
||||
@override
|
||||
String get securityProfileStrictDesc =>
|
||||
'Maximaler Schutz vor Phishing, kann aber unkomfortabel sein.';
|
||||
|
||||
@override
|
||||
String get settingsNotification => 'Benachrichtigung';
|
||||
|
||||
|
|
@ -1334,7 +1362,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
'Erfahre, wer dich anfragt';
|
||||
|
||||
@override
|
||||
String get userDiscoverySettingsManualApproval => 'Vor dem Teilen fragen';
|
||||
String get userDiscoverySettingsManualApproval => 'Vor jedem Teilen fragen';
|
||||
|
||||
@override
|
||||
String get userDiscoverySettingsManualApprovalDesc =>
|
||||
|
|
@ -1908,4 +1936,35 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get showUsername => 'Benutzernamen anzeigen';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionTitle => 'Wähle deinen Setup-Weg';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionSubtitle =>
|
||||
'Wähle aus, wie du deine Sicherheits- und Privatsphäre-Einstellungen konfigurieren möchtest.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultTitle => 'Standard';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultDesc =>
|
||||
'Wendet sofort die empfohlenen Einstellungen an, damit du die App direkt nutzen kannst.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultBadge => 'Schnelles Setup';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionCustomizeTitle => 'Anpassen';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionCustomizeDesc =>
|
||||
'Schritt-für-Schritt-Einrichtung, damit du selbst entscheiden kannst.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionStrictTitle => 'Erhöhter Schutz';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionStrictDesc =>
|
||||
'Maximaler Schutz vor Phishing. Empfohlen für *Journalisten & Personen des öffentlichen Lebens*.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,6 +288,34 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return '$len contact(s)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsPrivacyProfileSelectionTitle => 'Security Profile';
|
||||
|
||||
@override
|
||||
String get settingsPrivacyProfileSelectionDesc =>
|
||||
'Choose your setup path and security configuration';
|
||||
|
||||
@override
|
||||
String get securityProfileTitle => 'Security Profile';
|
||||
|
||||
@override
|
||||
String get securityProfileSubtitle =>
|
||||
'Choose the level of protection that fits your daily use. This can be changed at any time in your settings.';
|
||||
|
||||
@override
|
||||
String get securityProfileNormalTitle => 'Normal Protection';
|
||||
|
||||
@override
|
||||
String get securityProfileNormalDesc =>
|
||||
'Good balance between a convenient mode without bothering you too much.';
|
||||
|
||||
@override
|
||||
String get securityProfileStrictTitle => 'Strict Protection';
|
||||
|
||||
@override
|
||||
String get securityProfileStrictDesc =>
|
||||
'Maximum anti-phishing protection but may be inconvenient.';
|
||||
|
||||
@override
|
||||
String get settingsNotification => 'Notification';
|
||||
|
||||
|
|
@ -1325,7 +1353,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
'Be informed about who is requesting';
|
||||
|
||||
@override
|
||||
String get userDiscoverySettingsManualApproval => 'Ask before sharing';
|
||||
String get userDiscoverySettingsManualApproval =>
|
||||
'Ask every time before sharing';
|
||||
|
||||
@override
|
||||
String get userDiscoverySettingsManualApprovalDesc =>
|
||||
|
|
@ -1892,4 +1921,35 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get showUsername => 'Show username';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionTitle => 'Choose your setup path';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionSubtitle =>
|
||||
'Choose how you want to configure your security and privacy settings.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultTitle => 'Default';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultDesc =>
|
||||
'Instantly applies recommended settings so you can start using the app.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultBadge => 'Fast Setup';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionCustomizeTitle => 'Customize';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionCustomizeDesc =>
|
||||
'Step-by-step setup so you can decide for yourself.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionStrictTitle => 'Enhanced Protection';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionStrictDesc =>
|
||||
'Maximum anti-phishing defense. Recommended for *journalists & public figures*.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit f356d455e46d223ca2ea370892d23e54a6fe48c4
|
||||
Subproject commit 18aa5e1afc76a20ded04e9d2c4321fec1c91183d
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:twonly/src/services/profile.service.dart';
|
||||
part 'userdata.model.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
|
|
@ -10,9 +11,9 @@ class UserData {
|
|||
required this.displayName,
|
||||
required this.subscriptionPlan,
|
||||
required this.currentSetupPage,
|
||||
required this.appVersion,
|
||||
});
|
||||
factory UserData.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserDataFromJson(json);
|
||||
factory UserData.fromJson(Map<String, dynamic> json) => _$UserDataFromJson(json);
|
||||
|
||||
final int userId;
|
||||
|
||||
|
|
@ -35,6 +36,12 @@ class UserData {
|
|||
@JsonKey(defaultValue: 0)
|
||||
int deviceId = 0;
|
||||
|
||||
@JsonKey(defaultValue: SetupProfile.standard)
|
||||
SetupProfile setupProfile = SetupProfile.standard;
|
||||
|
||||
@JsonKey(defaultValue: SecurityProfile.normal)
|
||||
SecurityProfile securityProfile = SecurityProfile.normal;
|
||||
|
||||
// --- SUBSCRIPTION DTA ---
|
||||
|
||||
@JsonKey(defaultValue: 'Free')
|
||||
|
|
@ -179,8 +186,7 @@ class TwonlySafeBackup {
|
|||
required this.backupId,
|
||||
required this.encryptionKey,
|
||||
});
|
||||
factory TwonlySafeBackup.fromJson(Map<String, dynamic> json) =>
|
||||
_$TwonlySafeBackupFromJson(json);
|
||||
factory TwonlySafeBackup.fromJson(Map<String, dynamic> json) => _$TwonlySafeBackupFromJson(json);
|
||||
|
||||
int lastBackupSize = 0;
|
||||
LastBackupUploadState backupUploadState = LastBackupUploadState.none;
|
||||
|
|
|
|||
|
|
@ -13,13 +13,22 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
displayName: json['displayName'] as String,
|
||||
subscriptionPlan: json['subscriptionPlan'] as String? ?? 'Free',
|
||||
currentSetupPage: json['currentSetupPage'] as String?,
|
||||
appVersion: (json['appVersion'] as num?)?.toInt() ?? 0,
|
||||
)
|
||||
..avatarSvg = json['avatarSvg'] as String?
|
||||
..avatarJson = json['avatarJson'] as String?
|
||||
..appVersion = (json['appVersion'] as num?)?.toInt() ?? 0
|
||||
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
|
||||
..isDeveloper = json['isDeveloper'] as bool? ?? false
|
||||
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
|
||||
..setupProfile =
|
||||
$enumDecodeNullable(_$SetupProfileEnumMap, json['setupProfile']) ??
|
||||
SetupProfile.standard
|
||||
..securityProfile =
|
||||
$enumDecodeNullable(
|
||||
_$SecurityProfileEnumMap,
|
||||
json['securityProfile'],
|
||||
) ??
|
||||
SecurityProfile.normal
|
||||
..subscriptionPlanIdStore = json['subscriptionPlanIdStore'] as String?
|
||||
..lastImageSend = json['lastImageSend'] == null
|
||||
? null
|
||||
|
|
@ -115,6 +124,8 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'avatarCounter': instance.avatarCounter,
|
||||
'isDeveloper': instance.isDeveloper,
|
||||
'deviceId': instance.deviceId,
|
||||
'setupProfile': _$SetupProfileEnumMap[instance.setupProfile]!,
|
||||
'securityProfile': _$SecurityProfileEnumMap[instance.securityProfile]!,
|
||||
'subscriptionPlan': instance.subscriptionPlan,
|
||||
'subscriptionPlanIdStore': instance.subscriptionPlanIdStore,
|
||||
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
||||
|
|
@ -168,6 +179,17 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'hasZoomed': instance.hasZoomed,
|
||||
};
|
||||
|
||||
const _$SetupProfileEnumMap = {
|
||||
SetupProfile.standard: 'standard',
|
||||
SetupProfile.customized: 'customized',
|
||||
SetupProfile.maximum: 'maximum',
|
||||
};
|
||||
|
||||
const _$SecurityProfileEnumMap = {
|
||||
SecurityProfile.normal: 'normal',
|
||||
SecurityProfile.strict: 'strict',
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
ThemeMode.system: 'system',
|
||||
ThemeMode.light: 'light',
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import 'package:twonly/src/visual/views/settings/help/help.view.dart';
|
|||
import 'package:twonly/src/visual/views/settings/notification.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/privacy.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/privacy/block_users.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/privacy/profile_selection.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/privacy/user_discovery.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/profile/modify_avatar.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/profile/profile.view.dart';
|
||||
|
|
@ -205,6 +206,10 @@ final routerProvider = GoRouter(
|
|||
path: 'user_discovery',
|
||||
builder: (context, state) => const UserDiscoverySettingsView(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'profile_selection',
|
||||
builder: (context, state) => const ProfileSelectionSettingsView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserver.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||
as server;
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart' as server;
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart';
|
||||
import 'package:twonly/src/services/api/client2client/user_discovery.c2c.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
||||
|
|
@ -66,15 +65,13 @@ class ApiService {
|
|||
Stream<SubscriptionPlan> get onPlanUpdated => _planUpdateController.stream;
|
||||
|
||||
final _connectionStateController = StreamController<bool>.broadcast();
|
||||
Stream<bool> get onConnectionStateUpdated =>
|
||||
_connectionStateController.stream;
|
||||
Stream<bool> get onConnectionStateUpdated => _connectionStateController.stream;
|
||||
|
||||
final _appOutdatedController = StreamController<void>.broadcast();
|
||||
Stream<void> get onAppOutdated => _appOutdatedController.stream;
|
||||
|
||||
final _newDeviceRegisteredController = StreamController<void>.broadcast();
|
||||
Stream<void> get onNewDeviceRegistered =>
|
||||
_newDeviceRegisteredController.stream;
|
||||
Stream<void> get onNewDeviceRegistered => _newDeviceRegisteredController.stream;
|
||||
|
||||
bool appIsOutdated = false;
|
||||
bool isAuthenticated = false;
|
||||
|
|
@ -83,18 +80,12 @@ class ApiService {
|
|||
Timer? reconnectionTimer;
|
||||
int _reconnectionDelay = 5;
|
||||
|
||||
final HashMap<Int64, Completer<server.ServerToClient?>> _pendingRequests =
|
||||
HashMap();
|
||||
final HashMap<Int64, Completer<server.ServerToClient?>> _pendingRequests = HashMap();
|
||||
IOWebSocketChannel? _channel;
|
||||
// ignore: cancel_subscriptions
|
||||
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
|
||||
|
||||
Future<bool> _connectTo(String apiUrl) async {
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'DEBUG: ApiService._connectTo called with: $apiUrl (appIsOutdated=$appIsOutdated)',
|
||||
);
|
||||
}
|
||||
if (appIsOutdated) return false;
|
||||
try {
|
||||
final channel = IOWebSocketChannel.connect(
|
||||
|
|
@ -113,11 +104,8 @@ class ApiService {
|
|||
_channel!.stream.listen(_onData, onDone: _onDone, onError: _onError);
|
||||
Log.info('websocket connected to $apiUrl');
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
} catch (e) {
|
||||
_channel = null;
|
||||
if (kDebugMode) {
|
||||
print('DEBUG: _connectTo caught exception: $e\n$s');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -164,9 +152,6 @@ class ApiService {
|
|||
}
|
||||
|
||||
Future<void> onClosed() async {
|
||||
if (kDebugMode) {
|
||||
print('API onClosed called');
|
||||
}
|
||||
if (_channel == null) return;
|
||||
Log.info('websocket connection closed');
|
||||
_channel = null;
|
||||
|
|
@ -254,17 +239,11 @@ class ApiService {
|
|||
bool get isConnected => _channel != null && _channel!.closeCode == null;
|
||||
|
||||
Future<void> _onDone() async {
|
||||
if (kDebugMode) {
|
||||
print('API _onDone called');
|
||||
}
|
||||
_reconnectionDelay = 3;
|
||||
await onClosed();
|
||||
}
|
||||
|
||||
Future<void> _onError(dynamic e) async {
|
||||
if (kDebugMode) {
|
||||
print('API _onError called: $e');
|
||||
}
|
||||
if (e.toString().contains('Failed host lookup')) {
|
||||
Log.info('WebSocket connection failed: Host not reachable.');
|
||||
} else {
|
||||
|
|
@ -439,9 +418,7 @@ class ApiService {
|
|||
}
|
||||
if (res.error == ErrorCode.UserIdNotFound && contactId != null) {
|
||||
Log.warn('Contact deleted their account $contactId.');
|
||||
final contact = await twonlyDB.contactsDao
|
||||
.getContactByUserId(contactId)
|
||||
.getSingleOrNull();
|
||||
final contact = await twonlyDB.contactsDao.getContactByUserId(contactId).getSingleOrNull();
|
||||
if (contact != null) {
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
contactId,
|
||||
|
|
@ -506,8 +483,7 @@ class ApiService {
|
|||
return true;
|
||||
}
|
||||
if (result.isError) {
|
||||
if (result.error != ErrorCode.AuthTokenNotValid &&
|
||||
result.error != ErrorCode.ForegroundSessionConnected) {
|
||||
if (result.error != ErrorCode.AuthTokenNotValid && result.error != ErrorCode.ForegroundSessionConnected) {
|
||||
Log.error(
|
||||
'got error while authenticating to the server: ${result.error}',
|
||||
);
|
||||
|
|
@ -545,8 +521,7 @@ class ApiService {
|
|||
return true;
|
||||
}
|
||||
if (result.isError) {
|
||||
if (result.error != ErrorCode.AuthTokenNotValid &&
|
||||
result.error != ErrorCode.ForegroundSessionConnected) {
|
||||
if (result.error != ErrorCode.AuthTokenNotValid && result.error != ErrorCode.ForegroundSessionConnected) {
|
||||
Log.error(
|
||||
'got error while authenticating to the server: ${result.error}',
|
||||
);
|
||||
|
|
@ -578,8 +553,7 @@ class ApiService {
|
|||
return;
|
||||
}
|
||||
|
||||
final handshake = Handshake()
|
||||
..getAuthChallenge = Handshake_GetAuthChallenge();
|
||||
final handshake = Handshake()..getAuthChallenge = Handshake_GetAuthChallenge();
|
||||
final req = createClientToServerFromHandshake(handshake);
|
||||
|
||||
final result = await sendRequestSync(req, authenticated: false);
|
||||
|
|
@ -644,9 +618,7 @@ class ApiService {
|
|||
|
||||
final register = Handshake_Register()
|
||||
..username = username
|
||||
..publicIdentityKey = (await signalStore.getIdentityKeyPair())
|
||||
.getPublicKey()
|
||||
.serialize()
|
||||
..publicIdentityKey = (await signalStore.getIdentityKeyPair()).getPublicKey().serialize()
|
||||
..registrationId = Int64(signalIdentity.registrationId)
|
||||
..signedPrekey = signedPreKey.getKeyPair().publicKey.serialize()
|
||||
..signedPrekeySignature = signedPreKey.signature
|
||||
|
|
|
|||
7
lib/src/services/profile.service.dart
Normal file
7
lib/src/services/profile.service.dart
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
enum SetupProfile { standard, customized, maximum }
|
||||
|
||||
enum SecurityProfile { normal, strict }
|
||||
|
||||
extension SecurityProfileExtension on SecurityProfile {
|
||||
bool get showWarningForNonVerifiedContacts => this == SecurityProfile.strict;
|
||||
}
|
||||
|
|
@ -18,10 +18,13 @@ class Log {
|
|||
Logger.root.onRecord.listen((record) async {
|
||||
unawaited(_writeLogToFile(record));
|
||||
if (!kReleaseMode) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'${record.level.name} [${AppState.isInBackgroundTask ? 'b' : 'f'}] [twonly] ${record.loggerName} > ${record.message}',
|
||||
);
|
||||
if (!Platform.environment.containsKey('FLUTTER_TEST') ||
|
||||
record.level >= Level.WARNING) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'${record.level.name} [${AppState.isInBackgroundTask ? 'b' : 'f'}] [twonly] ${record.loggerName} > ${record.message}',
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/services/profile.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
|
@ -23,16 +25,18 @@ class VerificationBadgeInfo extends StatelessWidget {
|
|||
description: context.lang.verificationBadgeGreenDesc,
|
||||
boldTextColor: primaryColor,
|
||||
),
|
||||
_buildItem(
|
||||
context,
|
||||
icon: const SvgIcon(
|
||||
assetPath: SvgIcons.verifiedGreen,
|
||||
size: 40,
|
||||
color: colorVerificationBadgeYellow,
|
||||
if (userService.currentUser.securityProfile != SecurityProfile.strict ||
|
||||
userService.currentUser.isUserDiscoveryEnabled)
|
||||
_buildItem(
|
||||
context,
|
||||
icon: const SvgIcon(
|
||||
assetPath: SvgIcons.verifiedGreen,
|
||||
size: 40,
|
||||
color: colorVerificationBadgeYellow,
|
||||
),
|
||||
description: context.lang.verificationBadgeYellowDesc,
|
||||
boldTextColor: colorVerificationBadgeYellow,
|
||||
),
|
||||
description: context.lang.verificationBadgeYellowDesc,
|
||||
boldTextColor: colorVerificationBadgeYellow,
|
||||
),
|
||||
_buildItem(
|
||||
context,
|
||||
icon: const SvgIcon(assetPath: SvgIcons.verifiedRed, size: 40),
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ class CameraScannedOverlay extends StatelessWidget {
|
|||
...mainController.contactsVerified.values.map(
|
||||
(c) => _buildVerifiedContactTile(context, c),
|
||||
),
|
||||
if (mainController.scannedUrl != null)
|
||||
_buildScannedUrlTile(context, mainController.scannedUrl!),
|
||||
if (mainController.scannedUrl != null) _buildScannedUrlTile(context, mainController.scannedUrl!),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -46,15 +45,14 @@ class CameraScannedOverlay extends StatelessWidget {
|
|||
return GestureDetector(
|
||||
onTap: () async {
|
||||
c.isLoading = true;
|
||||
mainController.setState();
|
||||
mainController.setState?.call();
|
||||
|
||||
showSnackbar(
|
||||
context,
|
||||
context.lang.requestedUserToastText(c.profile.username),
|
||||
level: SnackbarLevel.success,
|
||||
);
|
||||
if (await addNewContactFromPublicProfile(c.profile) &&
|
||||
context.mounted) {
|
||||
if (await addNewContactFromPublicProfile(c.profile) && context.mounted) {
|
||||
// showSnackbar(
|
||||
// context,
|
||||
// context.lang.requestedUserToastText(c.profile.username),
|
||||
|
|
@ -121,13 +119,11 @@ class CameraScannedOverlay extends StatelessWidget {
|
|||
child: SizedBox(
|
||||
width: 30,
|
||||
child: Lottie.asset(
|
||||
c.verificationOk
|
||||
? 'assets/animations/success.lottie'
|
||||
: 'assets/animations/failed.lottie',
|
||||
c.verificationOk ? 'assets/animations/success.lottie' : 'assets/animations/failed.lottie',
|
||||
repeat: false,
|
||||
onLoaded: (p0) {
|
||||
Future.delayed(const Duration(seconds: 4), () {
|
||||
mainController.setState();
|
||||
mainController.setState?.call();
|
||||
});
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class ScannedNewProfile {
|
|||
}
|
||||
|
||||
class MainCameraController {
|
||||
late void Function() setState;
|
||||
void Function()? setState;
|
||||
CameraController? cameraController;
|
||||
ScreenshotController screenshotController = ScreenshotController();
|
||||
SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails();
|
||||
|
|
@ -61,12 +61,12 @@ class MainCameraController {
|
|||
|
||||
void setSharedLinkForPreview(Uri? url) {
|
||||
sharedLinkForPreview = url;
|
||||
setState();
|
||||
setState?.call();
|
||||
}
|
||||
|
||||
void onImageSend() {
|
||||
scannedUrl = '';
|
||||
setState();
|
||||
setState?.call();
|
||||
}
|
||||
|
||||
final BarcodeScanner _barcodeScanner = BarcodeScanner();
|
||||
|
|
@ -115,6 +115,7 @@ class MainCameraController {
|
|||
);
|
||||
initCameraStarted = false;
|
||||
selectedCameraDetails = SelectedCameraDetails();
|
||||
setState?.call();
|
||||
}
|
||||
|
||||
Future<void> selectCamera(int sCameraId, bool init) async {
|
||||
|
|
@ -153,8 +154,11 @@ class MainCameraController {
|
|||
try {
|
||||
_initializeFuture = cameraController?.initialize();
|
||||
await _initializeFuture;
|
||||
if (cameraController == null) return;
|
||||
await cameraController?.startImageStream(_processCameraImage);
|
||||
if (cameraController == null) return;
|
||||
await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor);
|
||||
if (cameraController == null) return;
|
||||
if (userService.currentUser.videoStabilizationEnabled && !kDebugMode) {
|
||||
await cameraController?.setVideoStabilizationMode(
|
||||
VideoStabilizationMode.level1,
|
||||
|
|
@ -172,10 +176,13 @@ class MainCameraController {
|
|||
} catch (e) {
|
||||
Log.info(e);
|
||||
}
|
||||
if (cameraController == null) return;
|
||||
selectedCameraDetails.scaleFactor = 1;
|
||||
|
||||
await cameraController?.setZoomLevel(1);
|
||||
if (cameraController == null) return;
|
||||
await cameraController?.setDescription(AppEnvironment.cameras[cameraId]);
|
||||
if (cameraController == null) return;
|
||||
try {
|
||||
if (!isVideoRecording) {
|
||||
await cameraController?.startImageStream(_processCameraImage);
|
||||
|
|
@ -186,12 +193,15 @@ class MainCameraController {
|
|||
}
|
||||
|
||||
try {
|
||||
if (cameraController == null) return;
|
||||
await cameraController?.lockCaptureOrientation(
|
||||
DeviceOrientation.portraitUp,
|
||||
);
|
||||
if (cameraController == null) return;
|
||||
await cameraController?.setFlashMode(
|
||||
selectedCameraDetails.isFlashOn ? FlashMode.always : FlashMode.off,
|
||||
);
|
||||
if (cameraController == null) return;
|
||||
selectedCameraDetails.maxAvailableZoom = await cameraController?.getMaxZoomLevel() ?? 1;
|
||||
selectedCameraDetails.minAvailableZoom = await cameraController?.getMinZoomLevel() ?? 1;
|
||||
selectedCameraDetails
|
||||
|
|
@ -204,7 +214,7 @@ class MainCameraController {
|
|||
isSelectingFaceFilters = false;
|
||||
setFilter(FaceFilterType.none);
|
||||
zoomButtonKey = GlobalKey();
|
||||
setState();
|
||||
setState?.call();
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
cameraController = null;
|
||||
|
|
@ -226,7 +236,7 @@ class MainCameraController {
|
|||
final dx = (localPosition.dx / box.size.width).clamp(0.0, 1.0);
|
||||
final dy = (localPosition.dy / box.size.height).clamp(0.0, 1.0);
|
||||
|
||||
setState();
|
||||
setState?.call();
|
||||
|
||||
await HapticFeedback.lightImpact();
|
||||
try {
|
||||
|
|
@ -244,7 +254,7 @@ class MainCameraController {
|
|||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
focusPointOffset = null;
|
||||
setState();
|
||||
setState?.call();
|
||||
}
|
||||
|
||||
void setFilter(FaceFilterType type) {
|
||||
|
|
@ -254,7 +264,7 @@ class MainCameraController {
|
|||
facePaint = null;
|
||||
_isBusyFaces = false;
|
||||
}
|
||||
setState();
|
||||
setState?.call();
|
||||
}
|
||||
|
||||
FaceFilterPainter? faceFilterPainter;
|
||||
|
|
@ -419,7 +429,7 @@ class MainCameraController {
|
|||
}
|
||||
}
|
||||
_isBusy = false;
|
||||
setState();
|
||||
setState?.call();
|
||||
}
|
||||
|
||||
Future<void> _processFaces(InputImage inputImage) async {
|
||||
|
|
@ -465,6 +475,6 @@ class MainCameraController {
|
|||
}
|
||||
}
|
||||
_isBusyFaces = false;
|
||||
setState();
|
||||
setState?.call();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_mainCameraController.setState = null;
|
||||
_mainCameraController.closeCamera();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_mainCameraController.setState = null;
|
||||
_mainCameraController.closeCamera();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
List<Group> _groupsPinned = [];
|
||||
List<Group> _groupsArchived = [];
|
||||
|
||||
bool _hasContacts = true;
|
||||
bool _hasContacts = false;
|
||||
bool get _hasOpenGroup => _groupsNotPinned.isNotEmpty || _groupsArchived.isNotEmpty || _groupsPinned.isNotEmpty;
|
||||
|
||||
GlobalKey searchForOtherUsers = GlobalKey();
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ class HomeViewState extends State<HomeView> {
|
|||
_homeViewPageIndexSub?.cancel();
|
||||
_selectNotificationSub?.cancel();
|
||||
_disableCameraTimer?.cancel();
|
||||
_mainCameraController.setState = null;
|
||||
_mainCameraController.closeCamera();
|
||||
_intentStreamSub?.cancel();
|
||||
_deepLinkSub?.cancel();
|
||||
|
|
|
|||
|
|
@ -140,7 +140,8 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
displayName: username,
|
||||
subscriptionPlan: 'Free',
|
||||
currentSetupPage: SetupPages.profile.name,
|
||||
)..appVersion = AppState.latestAppVersionId;
|
||||
appVersion: AppState.latestAppVersionId,
|
||||
);
|
||||
|
||||
await UserService.save(userData);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,14 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/services/profile.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/backup.setup.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/let_your_friends_find_you.setup.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/profile.setup.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/profile_selection.setup.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/security_profile.setup.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/share_your_friends.setup.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/verification_badge.setup.dart';
|
||||
import 'package:twonly/src/visual/views/settings/privacy/user_discovery/components/user_discovery_setup.comp.dart';
|
||||
|
|
@ -14,6 +17,8 @@ import 'package:twonly/src/visual/views/settings/privacy/user_discovery/componen
|
|||
enum SetupPages {
|
||||
profile,
|
||||
backup,
|
||||
profileSelection,
|
||||
securityProfile,
|
||||
verificationBadge,
|
||||
shareYourFriends,
|
||||
letYourFriendsFindYou,
|
||||
|
|
@ -27,25 +32,62 @@ extension SetupPagesExtension on SetupPages {
|
|||
);
|
||||
}
|
||||
|
||||
int get pageNumber => index + 1;
|
||||
int get totalPages => SetupPages.values.length;
|
||||
static List<SetupPages> get activePages {
|
||||
final setupProfile = userService.currentUser.setupProfile;
|
||||
switch (setupProfile) {
|
||||
case SetupProfile.standard:
|
||||
return [
|
||||
SetupPages.profile,
|
||||
SetupPages.backup,
|
||||
SetupPages.profileSelection,
|
||||
];
|
||||
case SetupProfile.maximum:
|
||||
return [
|
||||
SetupPages.profile,
|
||||
SetupPages.backup,
|
||||
SetupPages.profileSelection,
|
||||
SetupPages.verificationBadge,
|
||||
];
|
||||
case SetupProfile.customized:
|
||||
return [
|
||||
SetupPages.profile,
|
||||
SetupPages.backup,
|
||||
SetupPages.profileSelection,
|
||||
SetupPages.securityProfile,
|
||||
SetupPages.verificationBadge,
|
||||
SetupPages.shareYourFriends,
|
||||
SetupPages.letYourFriendsFindYou,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
int get pageNumber {
|
||||
final idx = activePages.indexOf(this);
|
||||
return idx != -1 ? idx + 1 : 1;
|
||||
}
|
||||
|
||||
int get totalPages => activePages.length;
|
||||
int get progressPercentage => ((pageNumber - 1) / totalPages * 100).round();
|
||||
String get progressText => '$pageNumber / $totalPages';
|
||||
|
||||
bool get isLast => index == SetupPages.values.length - 1;
|
||||
bool get isLast {
|
||||
return activePages.isNotEmpty && activePages.last == this;
|
||||
}
|
||||
|
||||
SetupPages? next() {
|
||||
final nextIndex = index + 1;
|
||||
if (nextIndex < SetupPages.values.length) {
|
||||
return SetupPages.values[nextIndex];
|
||||
final pages = activePages;
|
||||
final idx = pages.indexOf(this);
|
||||
if (idx != -1 && idx + 1 < pages.length) {
|
||||
return pages[idx + 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
SetupPages? previous() {
|
||||
final prevIndex = index - 1;
|
||||
if (prevIndex >= 0) {
|
||||
return SetupPages.values[prevIndex];
|
||||
final pages = activePages;
|
||||
final idx = pages.indexOf(this);
|
||||
if (idx > 0) {
|
||||
return pages[idx - 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -110,9 +152,7 @@ class _SetupViewState extends State<SetupView> {
|
|||
right: index == currentPage.totalPages - 1 ? 0 : 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isFinished
|
||||
? context.color.primary
|
||||
: context.color.surfaceContainer,
|
||||
color: isFinished ? context.color.primary : context.color.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
|
|
@ -149,8 +189,7 @@ class _SetupViewState extends State<SetupView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (currentPage.index > 0 && !currentPage.isLast)
|
||||
const SizedBox(width: 24),
|
||||
if (currentPage.index > 0 && !currentPage.isLast) const SizedBox(width: 24),
|
||||
if (!currentPage.isLast)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
|
|
@ -183,6 +222,10 @@ class _SetupViewState extends State<SetupView> {
|
|||
return const ProfileSetupPage();
|
||||
case SetupPages.backup:
|
||||
return const BackupSetupPage();
|
||||
case SetupPages.profileSelection:
|
||||
return const ProfileSelectionSetup();
|
||||
case SetupPages.securityProfile:
|
||||
return const SecurityProfileSetup();
|
||||
case SetupPages.verificationBadge:
|
||||
return const VerificationBadgeSetupPage();
|
||||
case SetupPages.shareYourFriends:
|
||||
|
|
|
|||
|
|
@ -18,43 +18,48 @@ class NextButtonComp extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentPage = SetupPagesExtension.fromStr(
|
||||
userService.currentUser.currentSetupPage,
|
||||
);
|
||||
return ElevatedButton(
|
||||
onPressed: (canSubmit && !isLoading)
|
||||
? () async {
|
||||
if (onPressed != null) {
|
||||
final error = await onPressed?.call();
|
||||
if (error == true) return;
|
||||
}
|
||||
await UserService.update((user) {
|
||||
user.currentSetupPage = currentPage.next()?.name;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 56),
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: context.color.onPrimary,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
height: 24,
|
||||
width: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
currentPage.isLast ? context.lang.finishSetup : context.lang.next,
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
return StreamBuilder<void>(
|
||||
stream: userService.onUserUpdated,
|
||||
builder: (context, snapshot) {
|
||||
final currentPage = SetupPagesExtension.fromStr(
|
||||
userService.currentUser.currentSetupPage,
|
||||
);
|
||||
return ElevatedButton(
|
||||
onPressed: (canSubmit && !isLoading)
|
||||
? () async {
|
||||
if (onPressed != null) {
|
||||
final error = await onPressed?.call();
|
||||
if (error == true) return;
|
||||
}
|
||||
await UserService.update((user) {
|
||||
user.currentSetupPage = currentPage.next()?.name;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 56),
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: context.color.onPrimary,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
height: 24,
|
||||
width: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
currentPage.isLast ? context.lang.finishSetup : context.lang.next,
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,192 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/services/profile.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class SafetyProfileCard extends StatelessWidget {
|
||||
const SafetyProfileCard({
|
||||
required this.profile,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
this.isHovered = false,
|
||||
this.onHover,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Object profile;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final bool isHovered;
|
||||
final ValueChanged<bool>? onHover;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final bodyMediumStyle = theme.textTheme.bodyMedium?.copyWith(
|
||||
color: context.color.onSurfaceVariant,
|
||||
height: 1.35,
|
||||
);
|
||||
|
||||
final String title;
|
||||
final Widget subtitle;
|
||||
final IconData icon;
|
||||
final String? badgeText;
|
||||
|
||||
if (profile is SetupProfile) {
|
||||
switch (profile as SetupProfile) {
|
||||
case SetupProfile.standard:
|
||||
title = context.lang.onboardingProfileSelectionDefaultTitle;
|
||||
subtitle = Text(
|
||||
context.lang.onboardingProfileSelectionDefaultDesc,
|
||||
style: bodyMediumStyle,
|
||||
);
|
||||
icon = Icons.bolt_rounded;
|
||||
badgeText = context.lang.onboardingProfileSelectionDefaultBadge;
|
||||
case SetupProfile.customized:
|
||||
title = context.lang.onboardingProfileSelectionCustomizeTitle;
|
||||
subtitle = Text(
|
||||
context.lang.onboardingProfileSelectionCustomizeDesc,
|
||||
style: bodyMediumStyle,
|
||||
);
|
||||
icon = Icons.tune_rounded;
|
||||
badgeText = null;
|
||||
case SetupProfile.maximum:
|
||||
title = context.lang.onboardingProfileSelectionStrictTitle;
|
||||
subtitle = RichText(
|
||||
text: TextSpan(
|
||||
style: bodyMediumStyle,
|
||||
children: formattedText(
|
||||
context,
|
||||
context.lang.onboardingProfileSelectionStrictDesc,
|
||||
boldTextColor: context.color.onSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
icon = Icons.lock_outline_rounded;
|
||||
badgeText = null;
|
||||
}
|
||||
} else if (profile is SecurityProfile) {
|
||||
switch (profile as SecurityProfile) {
|
||||
case SecurityProfile.normal:
|
||||
title = context.lang.securityProfileNormalTitle;
|
||||
subtitle = Text(
|
||||
context.lang.securityProfileNormalDesc,
|
||||
style: bodyMediumStyle,
|
||||
);
|
||||
icon = Icons.shield_outlined;
|
||||
badgeText = null;
|
||||
case SecurityProfile.strict:
|
||||
title = context.lang.securityProfileStrictTitle;
|
||||
subtitle = Text(
|
||||
context.lang.securityProfileStrictDesc,
|
||||
style: bodyMediumStyle,
|
||||
);
|
||||
icon = Icons.verified_user_outlined;
|
||||
badgeText = null;
|
||||
}
|
||||
} else {
|
||||
throw ArgumentError('Invalid profile type: $profile');
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
onEnter: (_) => onHover?.call(true),
|
||||
onExit: (_) => onHover?.call(false),
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
curve: Curves.easeInOut,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? context.color.primaryContainer.withValues(alpha: 0.12)
|
||||
: context.color.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? context.color.primary
|
||||
: (isHovered
|
||||
? context.color.onSurfaceVariant.withValues(alpha: 0.3)
|
||||
: context.color.outlineVariant.withValues(alpha: 0.4)),
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
boxShadow: isSelected
|
||||
? [
|
||||
BoxShadow(
|
||||
color: context.color.primary.withValues(alpha: 0.06),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? context.color.primary.withValues(alpha: 0.1)
|
||||
: context.color.surfaceContainerHigh,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: isSelected
|
||||
? context.color.primary
|
||||
: context.color.onSurfaceVariant,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isSelected
|
||||
? context.color.primary
|
||||
: theme.textTheme.titleMedium?.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (badgeText != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.primary,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
badgeText,
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: context.color.onPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
subtitle,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/services/profile.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/components/next_button.comp.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/components/profile_card.comp.dart';
|
||||
import 'package:twonly/src/visual/views/settings/privacy/user_discovery/components/user_discovery_setup.comp.dart';
|
||||
|
||||
class ProfileSelectionSetup extends StatefulWidget {
|
||||
const ProfileSelectionSetup({super.key});
|
||||
|
||||
@override
|
||||
State<ProfileSelectionSetup> createState() => _ProfileSelectionSetupState();
|
||||
}
|
||||
|
||||
class _ProfileSelectionSetupState extends State<ProfileSelectionSetup> {
|
||||
SetupProfile? _hoveredProfile;
|
||||
bool _isLoading = false;
|
||||
|
||||
Future<void> _onProfileTapped(SetupProfile profile) async {
|
||||
await UserService.update((user) {
|
||||
user.setupProfile = profile;
|
||||
if (profile == SetupProfile.standard) {
|
||||
user.securityProfile = SecurityProfile.normal;
|
||||
} else if (profile == SetupProfile.maximum) {
|
||||
user.securityProfile = SecurityProfile.strict;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<void>(
|
||||
stream: userService.onUserUpdated,
|
||||
builder: (context, snapshot) {
|
||||
final user = userService.currentUser;
|
||||
final selectedProfile = user.setupProfile;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.onboardingProfileSelectionTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.lang.onboardingProfileSelectionSubtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
SafetyProfileCard(
|
||||
profile: SetupProfile.standard,
|
||||
isSelected: selectedProfile == SetupProfile.standard,
|
||||
isHovered: _hoveredProfile == SetupProfile.standard,
|
||||
onHover: (hovered) => setState(() {
|
||||
_hoveredProfile = hovered ? SetupProfile.standard : null;
|
||||
}),
|
||||
onTap: () => _onProfileTapped(SetupProfile.standard),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SafetyProfileCard(
|
||||
profile: SetupProfile.customized,
|
||||
isSelected: selectedProfile == SetupProfile.customized,
|
||||
isHovered: _hoveredProfile == SetupProfile.customized,
|
||||
onHover: (hovered) => setState(() {
|
||||
_hoveredProfile = hovered ? SetupProfile.customized : null;
|
||||
}),
|
||||
onTap: () => _onProfileTapped(SetupProfile.customized),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SafetyProfileCard(
|
||||
profile: SetupProfile.maximum,
|
||||
isSelected: selectedProfile == SetupProfile.maximum,
|
||||
isHovered: _hoveredProfile == SetupProfile.maximum,
|
||||
onHover: (hovered) => setState(() {
|
||||
_hoveredProfile = hovered ? SetupProfile.maximum : null;
|
||||
}),
|
||||
onTap: () => _onProfileTapped(SetupProfile.maximum),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
NextButtonComp(
|
||||
key: ValueKey(selectedProfile),
|
||||
isLoading: _isLoading,
|
||||
onPressed: () async {
|
||||
if (selectedProfile == SetupProfile.standard) {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
await UserDiscoverySetupState().initializeOrUpdate();
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/services/profile.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/components/next_button.comp.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/components/profile_card.comp.dart';
|
||||
|
||||
class SecurityProfileSetup extends StatefulWidget {
|
||||
const SecurityProfileSetup({super.key});
|
||||
|
||||
@override
|
||||
State<SecurityProfileSetup> createState() => _SecurityProfileSetupState();
|
||||
}
|
||||
|
||||
class _SecurityProfileSetupState extends State<SecurityProfileSetup> {
|
||||
SecurityProfile? _hoveredProfile;
|
||||
|
||||
Future<void> _onProfileTapped(SecurityProfile profile) async {
|
||||
await UserService.update((user) {
|
||||
user.securityProfile = profile;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<void>(
|
||||
stream: userService.onUserUpdated,
|
||||
builder: (context, snapshot) {
|
||||
final user = userService.currentUser;
|
||||
final selectedProfile = user.securityProfile;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.securityProfileTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.lang.securityProfileSubtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
SafetyProfileCard(
|
||||
profile: SecurityProfile.normal,
|
||||
isSelected: selectedProfile == SecurityProfile.normal,
|
||||
isHovered: _hoveredProfile == SecurityProfile.normal,
|
||||
onHover: (hovered) => setState(() {
|
||||
_hoveredProfile = hovered ? SecurityProfile.normal : null;
|
||||
}),
|
||||
onTap: () => _onProfileTapped(SecurityProfile.normal),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SafetyProfileCard(
|
||||
profile: SecurityProfile.strict,
|
||||
isSelected: selectedProfile == SecurityProfile.strict,
|
||||
isHovered: _hoveredProfile == SecurityProfile.strict,
|
||||
onHover: (hovered) => setState(() {
|
||||
_hoveredProfile = hovered ? SecurityProfile.strict : null;
|
||||
}),
|
||||
onTap: () => _onProfileTapped(SecurityProfile.strict),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
const NextButtonComp(),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/services/profile.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
|
|
@ -78,6 +79,18 @@ class _PrivacyViewState extends State<PrivacyView> {
|
|||
setState(() {});
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(context.lang.settingsPrivacyProfileSelectionTitle),
|
||||
subtitle: Text(
|
||||
userService.currentUser.securityProfile == SecurityProfile.strict
|
||||
? context.lang.securityProfileStrictTitle
|
||||
: context.lang.securityProfileNormalTitle,
|
||||
),
|
||||
onTap: () async {
|
||||
await context.push(Routes.settingsPrivacyProfileSelection);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: Text(context.lang.settingsTypingIndication),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/services/profile.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/components/profile_card.comp.dart';
|
||||
|
||||
class ProfileSelectionSettingsView extends StatefulWidget {
|
||||
const ProfileSelectionSettingsView({super.key});
|
||||
|
||||
@override
|
||||
State<ProfileSelectionSettingsView> createState() =>
|
||||
_ProfileSelectionSettingsViewState();
|
||||
}
|
||||
|
||||
class _ProfileSelectionSettingsViewState
|
||||
extends State<ProfileSelectionSettingsView> {
|
||||
SecurityProfile? _hoveredProfile;
|
||||
|
||||
Future<void> _onProfileTapped(SecurityProfile profile) async {
|
||||
await UserService.update((user) {
|
||||
user.securityProfile = profile;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.securityProfileTitle),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: StreamBuilder<void>(
|
||||
stream: userService.onUserUpdated,
|
||||
builder: (context, snapshot) {
|
||||
final user = userService.currentUser;
|
||||
final selectedProfile = user.securityProfile;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.securityProfileSubtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
SafetyProfileCard(
|
||||
profile: SecurityProfile.normal,
|
||||
isSelected: selectedProfile == SecurityProfile.normal,
|
||||
isHovered: _hoveredProfile == SecurityProfile.normal,
|
||||
onHover: (hovered) => setState(() {
|
||||
_hoveredProfile = hovered ? SecurityProfile.normal : null;
|
||||
}),
|
||||
onTap: () => _onProfileTapped(SecurityProfile.normal),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SafetyProfileCard(
|
||||
profile: SecurityProfile.strict,
|
||||
isSelected: selectedProfile == SecurityProfile.strict,
|
||||
isHovered: _hoveredProfile == SecurityProfile.strict,
|
||||
onHover: (hovered) => setState(() {
|
||||
_hoveredProfile = hovered ? SecurityProfile.strict : null;
|
||||
}),
|
||||
onTap: () => _onProfileTapped(SecurityProfile.strict),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ List<String> getExampleUsers(BuildContext context) => [
|
|||
|
||||
class UserDiscoverySetupState {
|
||||
UserDiscoverySetupState({
|
||||
required this.setState,
|
||||
this.setState,
|
||||
this.isUserDiscoveryEnabled = true,
|
||||
this.sharePromotion = true,
|
||||
this.isManualApprovalEnabled = false,
|
||||
|
|
@ -43,13 +43,17 @@ class UserDiscoverySetupState {
|
|||
bool isManualApprovalEnabled;
|
||||
int requiredSendImages;
|
||||
|
||||
void Function(void Function()) setState;
|
||||
void Function(void Function())? setState;
|
||||
|
||||
void update(void Function() update) {
|
||||
update();
|
||||
setState(() {
|
||||
if (setState != null) {
|
||||
setState!(() {
|
||||
wasChanged = true;
|
||||
});
|
||||
} else {
|
||||
wasChanged = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> initializeOrUpdate() async {
|
||||
|
|
|
|||
|
|
@ -34,10 +34,16 @@ pub(crate) async fn init_tracing(logs_dir: &std::path::Path, is_dart_available:
|
|||
|
||||
// Replace stdout with our new DartWriter!
|
||||
|
||||
let default_filter = if std::env::var("FLUTTER_TEST").is_ok() {
|
||||
"info,refinery_core=warn,refinery=warn"
|
||||
} else {
|
||||
"debug,refinery_core=warn,refinery=warn"
|
||||
};
|
||||
|
||||
let registry = Registry::default()
|
||||
.with(
|
||||
EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new("debug,refinery_core=warn,refinery=warn")),
|
||||
.unwrap_or_else(|_| EnvFilter::new(default_filter)),
|
||||
)
|
||||
.with(stdout_layer);
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,8 @@ void main() {
|
|||
displayName: 'Test User',
|
||||
subscriptionPlan: 'Free',
|
||||
currentSetupPage: null,
|
||||
)..appVersion = 62;
|
||||
appVersion: 62,
|
||||
);
|
||||
});
|
||||
|
||||
test('test flame counter', () async {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ void setupPlatformChannelMocks() {
|
|||
Future<dynamic> mockHandler(MethodCall methodCall) async {
|
||||
final userId = Zone.current[#userId] as int?;
|
||||
final keyPrefix = userId != null ? '${userId}_' : '';
|
||||
print(
|
||||
'DEBUG: mockHandler: method=${methodCall.method}, key=${methodCall.arguments?['key']}, userId=$userId, prefix=$keyPrefix',
|
||||
);
|
||||
if (methodCall.method == 'read') {
|
||||
final key = methodCall.arguments['key'] as String;
|
||||
return secureStorageMock[keyPrefix + key];
|
||||
|
|
@ -43,43 +40,38 @@ void setupPlatformChannelMocks() {
|
|||
return null;
|
||||
}
|
||||
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel(
|
||||
'plugins.it_crowd.double_tapp/flutter_secure_storage',
|
||||
),
|
||||
mockHandler,
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'),
|
||||
mockHandler,
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('dev.fluttercommunity.plus/connectivity'),
|
||||
(call) async {
|
||||
if (call.method == 'check') {
|
||||
return ['wifi'];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel(
|
||||
'be.tramesch.workmanager/foreground_channel_workmanager',
|
||||
),
|
||||
(call) async => true,
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('com.bbflight.background_downloader'),
|
||||
(call) async {
|
||||
if (call.method == 'enqueue') {
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel(
|
||||
'plugins.it_crowd.double_tapp/flutter_secure_storage',
|
||||
),
|
||||
mockHandler,
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'),
|
||||
mockHandler,
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel('dev.fluttercommunity.plus/connectivity'),
|
||||
(call) async {
|
||||
if (call.method == 'check') {
|
||||
return ['wifi'];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel(
|
||||
'be.tramesch.workmanager/foreground_channel_workmanager',
|
||||
),
|
||||
(call) async => true,
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel('com.bbflight.background_downloader'),
|
||||
(call) async {
|
||||
if (call.method == 'enqueue') {
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,9 +79,8 @@ class TestClient {
|
|||
displayName: username,
|
||||
subscriptionPlan: 'Free',
|
||||
currentSetupPage: null,
|
||||
appVersion: 100,
|
||||
);
|
||||
// ignore: cascade_invocations
|
||||
userData.appVersion = 100;
|
||||
await UserService.save(userData);
|
||||
|
||||
await api.authenticate();
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ class UserEnvironment {
|
|||
displayName: '$username Display',
|
||||
subscriptionPlan: 'Free',
|
||||
currentSetupPage: null,
|
||||
)..appVersion = 100;
|
||||
appVersion: 100,
|
||||
);
|
||||
|
||||
// ignore: cascade_invocations
|
||||
us.isUserCreated = true;
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ void main() {
|
|||
displayName: 'Test User',
|
||||
subscriptionPlan: 'Free',
|
||||
currentSetupPage: null,
|
||||
)..appVersion = 100;
|
||||
appVersion: 100,
|
||||
);
|
||||
userService.isUserCreated = true;
|
||||
await UserService.save(userService.currentUser);
|
||||
initialUserData = (await KeyValueStore.get('user'))!;
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ void main() {
|
|||
displayName: 'Test User',
|
||||
subscriptionPlan: 'Free',
|
||||
currentSetupPage: null,
|
||||
)..appVersion = 100;
|
||||
appVersion: 100,
|
||||
);
|
||||
userService.isUserCreated = true;
|
||||
AppEnvironment.initTesting();
|
||||
// Log.init();
|
||||
|
|
|
|||
Loading…
Reference in a new issue