mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 02:32:11 +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
|
## 0.2.17
|
||||||
|
|
||||||
- New: Adds an "Ask a Friend" button to new contact suggestions.
|
- New: Adds an "Ask a Friend" button to new contact suggestions.
|
||||||
|
- New: Adds security profiles.
|
||||||
- Improved: Onboarding flow for new users.
|
- Improved: Onboarding flow for new users.
|
||||||
- Improved: The blue verification checkmark now displays the total number of verifications.
|
- Improved: The blue verification checkmark now displays the total number of verifications.
|
||||||
- Fix: Issue with receiving messages when user closed app while decrypting
|
- Fix: Issue with receiving messages when user closed app while decrypting
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
@ -15,7 +16,8 @@ class LoggingCallbacks {
|
||||||
Log.info(log.split('INFO ')[1]);
|
Log.info(log.split('INFO ')[1]);
|
||||||
} else if (log.contains('DEBUG ')) {
|
} else if (log.contains('DEBUG ')) {
|
||||||
Log.info(log.split('DEBUG ')[1]);
|
Log.info(log.split('DEBUG ')[1]);
|
||||||
} else if (kDebugMode) {
|
} else if (kDebugMode && !Platform.environment.containsKey('FLUTTER_TEST')) {
|
||||||
|
// ignore: avoid_print
|
||||||
print(log);
|
print(log);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ class Routes {
|
||||||
'/settings/privacy/block_users';
|
'/settings/privacy/block_users';
|
||||||
static const String settingsPrivacyUserDiscovery =
|
static const String settingsPrivacyUserDiscovery =
|
||||||
'/settings/privacy/user_discovery';
|
'/settings/privacy/user_discovery';
|
||||||
|
static const String settingsPrivacyProfileSelection =
|
||||||
|
'/settings/privacy/profile_selection';
|
||||||
static const String settingsNotification = '/settings/notification';
|
static const String settingsNotification = '/settings/notification';
|
||||||
static const String settingsStorage = '/settings/storage_data';
|
static const String settingsStorage = '/settings/storage_data';
|
||||||
static const String settingsStorageManage = '/settings/storage_data/manage';
|
static const String settingsStorageManage = '/settings/storage_data/manage';
|
||||||
|
|
|
||||||
|
|
@ -626,6 +626,54 @@ abstract class AppLocalizations {
|
||||||
/// **'{len} contact(s)'**
|
/// **'{len} contact(s)'**
|
||||||
String settingsPrivacyBlockUsersCount(Object len);
|
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.
|
/// No description provided for @settingsNotification.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -2441,7 +2489,7 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @userDiscoverySettingsManualApproval.
|
/// No description provided for @userDiscoverySettingsManualApproval.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Ask before sharing'**
|
/// **'Ask every time before sharing'**
|
||||||
String get userDiscoverySettingsManualApproval;
|
String get userDiscoverySettingsManualApproval;
|
||||||
|
|
||||||
/// No description provided for @userDiscoverySettingsManualApprovalDesc.
|
/// No description provided for @userDiscoverySettingsManualApprovalDesc.
|
||||||
|
|
@ -3355,6 +3403,60 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Show username'**
|
/// **'Show username'**
|
||||||
String get showUsername;
|
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
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,34 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
return '$len Kontakt(e)';
|
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
|
@override
|
||||||
String get settingsNotification => 'Benachrichtigung';
|
String get settingsNotification => 'Benachrichtigung';
|
||||||
|
|
||||||
|
|
@ -1334,7 +1362,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
'Erfahre, wer dich anfragt';
|
'Erfahre, wer dich anfragt';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get userDiscoverySettingsManualApproval => 'Vor dem Teilen fragen';
|
String get userDiscoverySettingsManualApproval => 'Vor jedem Teilen fragen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get userDiscoverySettingsManualApprovalDesc =>
|
String get userDiscoverySettingsManualApprovalDesc =>
|
||||||
|
|
@ -1908,4 +1936,35 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get showUsername => 'Benutzernamen anzeigen';
|
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)';
|
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
|
@override
|
||||||
String get settingsNotification => 'Notification';
|
String get settingsNotification => 'Notification';
|
||||||
|
|
||||||
|
|
@ -1325,7 +1353,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
'Be informed about who is requesting';
|
'Be informed about who is requesting';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get userDiscoverySettingsManualApproval => 'Ask before sharing';
|
String get userDiscoverySettingsManualApproval =>
|
||||||
|
'Ask every time before sharing';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get userDiscoverySettingsManualApprovalDesc =>
|
String get userDiscoverySettingsManualApprovalDesc =>
|
||||||
|
|
@ -1892,4 +1921,35 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get showUsername => 'Show username';
|
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:flutter/material.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:twonly/src/services/profile.service.dart';
|
||||||
part 'userdata.model.g.dart';
|
part 'userdata.model.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
@ -10,9 +11,9 @@ class UserData {
|
||||||
required this.displayName,
|
required this.displayName,
|
||||||
required this.subscriptionPlan,
|
required this.subscriptionPlan,
|
||||||
required this.currentSetupPage,
|
required this.currentSetupPage,
|
||||||
|
required this.appVersion,
|
||||||
});
|
});
|
||||||
factory UserData.fromJson(Map<String, dynamic> json) =>
|
factory UserData.fromJson(Map<String, dynamic> json) => _$UserDataFromJson(json);
|
||||||
_$UserDataFromJson(json);
|
|
||||||
|
|
||||||
final int userId;
|
final int userId;
|
||||||
|
|
||||||
|
|
@ -35,6 +36,12 @@ class UserData {
|
||||||
@JsonKey(defaultValue: 0)
|
@JsonKey(defaultValue: 0)
|
||||||
int deviceId = 0;
|
int deviceId = 0;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: SetupProfile.standard)
|
||||||
|
SetupProfile setupProfile = SetupProfile.standard;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: SecurityProfile.normal)
|
||||||
|
SecurityProfile securityProfile = SecurityProfile.normal;
|
||||||
|
|
||||||
// --- SUBSCRIPTION DTA ---
|
// --- SUBSCRIPTION DTA ---
|
||||||
|
|
||||||
@JsonKey(defaultValue: 'Free')
|
@JsonKey(defaultValue: 'Free')
|
||||||
|
|
@ -179,8 +186,7 @@ class TwonlySafeBackup {
|
||||||
required this.backupId,
|
required this.backupId,
|
||||||
required this.encryptionKey,
|
required this.encryptionKey,
|
||||||
});
|
});
|
||||||
factory TwonlySafeBackup.fromJson(Map<String, dynamic> json) =>
|
factory TwonlySafeBackup.fromJson(Map<String, dynamic> json) => _$TwonlySafeBackupFromJson(json);
|
||||||
_$TwonlySafeBackupFromJson(json);
|
|
||||||
|
|
||||||
int lastBackupSize = 0;
|
int lastBackupSize = 0;
|
||||||
LastBackupUploadState backupUploadState = LastBackupUploadState.none;
|
LastBackupUploadState backupUploadState = LastBackupUploadState.none;
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,22 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
||||||
displayName: json['displayName'] as String,
|
displayName: json['displayName'] as String,
|
||||||
subscriptionPlan: json['subscriptionPlan'] as String? ?? 'Free',
|
subscriptionPlan: json['subscriptionPlan'] as String? ?? 'Free',
|
||||||
currentSetupPage: json['currentSetupPage'] as String?,
|
currentSetupPage: json['currentSetupPage'] as String?,
|
||||||
|
appVersion: (json['appVersion'] as num?)?.toInt() ?? 0,
|
||||||
)
|
)
|
||||||
..avatarSvg = json['avatarSvg'] as String?
|
..avatarSvg = json['avatarSvg'] as String?
|
||||||
..avatarJson = json['avatarJson'] as String?
|
..avatarJson = json['avatarJson'] as String?
|
||||||
..appVersion = (json['appVersion'] as num?)?.toInt() ?? 0
|
|
||||||
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
|
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
|
||||||
..isDeveloper = json['isDeveloper'] as bool? ?? false
|
..isDeveloper = json['isDeveloper'] as bool? ?? false
|
||||||
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
|
..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?
|
..subscriptionPlanIdStore = json['subscriptionPlanIdStore'] as String?
|
||||||
..lastImageSend = json['lastImageSend'] == null
|
..lastImageSend = json['lastImageSend'] == null
|
||||||
? null
|
? null
|
||||||
|
|
@ -115,6 +124,8 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'avatarCounter': instance.avatarCounter,
|
'avatarCounter': instance.avatarCounter,
|
||||||
'isDeveloper': instance.isDeveloper,
|
'isDeveloper': instance.isDeveloper,
|
||||||
'deviceId': instance.deviceId,
|
'deviceId': instance.deviceId,
|
||||||
|
'setupProfile': _$SetupProfileEnumMap[instance.setupProfile]!,
|
||||||
|
'securityProfile': _$SecurityProfileEnumMap[instance.securityProfile]!,
|
||||||
'subscriptionPlan': instance.subscriptionPlan,
|
'subscriptionPlan': instance.subscriptionPlan,
|
||||||
'subscriptionPlanIdStore': instance.subscriptionPlanIdStore,
|
'subscriptionPlanIdStore': instance.subscriptionPlanIdStore,
|
||||||
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
||||||
|
|
@ -168,6 +179,17 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'hasZoomed': instance.hasZoomed,
|
'hasZoomed': instance.hasZoomed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _$SetupProfileEnumMap = {
|
||||||
|
SetupProfile.standard: 'standard',
|
||||||
|
SetupProfile.customized: 'customized',
|
||||||
|
SetupProfile.maximum: 'maximum',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$SecurityProfileEnumMap = {
|
||||||
|
SecurityProfile.normal: 'normal',
|
||||||
|
SecurityProfile.strict: 'strict',
|
||||||
|
};
|
||||||
|
|
||||||
const _$ThemeModeEnumMap = {
|
const _$ThemeModeEnumMap = {
|
||||||
ThemeMode.system: 'system',
|
ThemeMode.system: 'system',
|
||||||
ThemeMode.light: 'light',
|
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/notification.view.dart';
|
||||||
import 'package:twonly/src/visual/views/settings/privacy.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/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/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/modify_avatar.view.dart';
|
||||||
import 'package:twonly/src/visual/views/settings/profile/profile.view.dart';
|
import 'package:twonly/src/visual/views/settings/profile/profile.view.dart';
|
||||||
|
|
@ -205,6 +206,10 @@ final routerProvider = GoRouter(
|
||||||
path: 'user_discovery',
|
path: 'user_discovery',
|
||||||
builder: (context, state) => const UserDiscoverySettingsView(),
|
builder: (context, state) => const UserDiscoverySettingsView(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: 'profile_selection',
|
||||||
|
builder: (context, state) => const ProfileSelectionSettingsView(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,7 @@ import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.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/client_to_server.pbserver.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.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'
|
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart' as server;
|
||||||
as server;
|
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart';
|
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/client2client/user_discovery.c2c.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
||||||
|
|
@ -66,15 +65,13 @@ class ApiService {
|
||||||
Stream<SubscriptionPlan> get onPlanUpdated => _planUpdateController.stream;
|
Stream<SubscriptionPlan> get onPlanUpdated => _planUpdateController.stream;
|
||||||
|
|
||||||
final _connectionStateController = StreamController<bool>.broadcast();
|
final _connectionStateController = StreamController<bool>.broadcast();
|
||||||
Stream<bool> get onConnectionStateUpdated =>
|
Stream<bool> get onConnectionStateUpdated => _connectionStateController.stream;
|
||||||
_connectionStateController.stream;
|
|
||||||
|
|
||||||
final _appOutdatedController = StreamController<void>.broadcast();
|
final _appOutdatedController = StreamController<void>.broadcast();
|
||||||
Stream<void> get onAppOutdated => _appOutdatedController.stream;
|
Stream<void> get onAppOutdated => _appOutdatedController.stream;
|
||||||
|
|
||||||
final _newDeviceRegisteredController = StreamController<void>.broadcast();
|
final _newDeviceRegisteredController = StreamController<void>.broadcast();
|
||||||
Stream<void> get onNewDeviceRegistered =>
|
Stream<void> get onNewDeviceRegistered => _newDeviceRegisteredController.stream;
|
||||||
_newDeviceRegisteredController.stream;
|
|
||||||
|
|
||||||
bool appIsOutdated = false;
|
bool appIsOutdated = false;
|
||||||
bool isAuthenticated = false;
|
bool isAuthenticated = false;
|
||||||
|
|
@ -83,18 +80,12 @@ class ApiService {
|
||||||
Timer? reconnectionTimer;
|
Timer? reconnectionTimer;
|
||||||
int _reconnectionDelay = 5;
|
int _reconnectionDelay = 5;
|
||||||
|
|
||||||
final HashMap<Int64, Completer<server.ServerToClient?>> _pendingRequests =
|
final HashMap<Int64, Completer<server.ServerToClient?>> _pendingRequests = HashMap();
|
||||||
HashMap();
|
|
||||||
IOWebSocketChannel? _channel;
|
IOWebSocketChannel? _channel;
|
||||||
// ignore: cancel_subscriptions
|
// ignore: cancel_subscriptions
|
||||||
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
|
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
|
||||||
|
|
||||||
Future<bool> _connectTo(String apiUrl) async {
|
Future<bool> _connectTo(String apiUrl) async {
|
||||||
if (kDebugMode) {
|
|
||||||
print(
|
|
||||||
'DEBUG: ApiService._connectTo called with: $apiUrl (appIsOutdated=$appIsOutdated)',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (appIsOutdated) return false;
|
if (appIsOutdated) return false;
|
||||||
try {
|
try {
|
||||||
final channel = IOWebSocketChannel.connect(
|
final channel = IOWebSocketChannel.connect(
|
||||||
|
|
@ -113,11 +104,8 @@ class ApiService {
|
||||||
_channel!.stream.listen(_onData, onDone: _onDone, onError: _onError);
|
_channel!.stream.listen(_onData, onDone: _onDone, onError: _onError);
|
||||||
Log.info('websocket connected to $apiUrl');
|
Log.info('websocket connected to $apiUrl');
|
||||||
return true;
|
return true;
|
||||||
} catch (e, s) {
|
} catch (e) {
|
||||||
_channel = null;
|
_channel = null;
|
||||||
if (kDebugMode) {
|
|
||||||
print('DEBUG: _connectTo caught exception: $e\n$s');
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,9 +152,6 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onClosed() async {
|
Future<void> onClosed() async {
|
||||||
if (kDebugMode) {
|
|
||||||
print('API onClosed called');
|
|
||||||
}
|
|
||||||
if (_channel == null) return;
|
if (_channel == null) return;
|
||||||
Log.info('websocket connection closed');
|
Log.info('websocket connection closed');
|
||||||
_channel = null;
|
_channel = null;
|
||||||
|
|
@ -254,17 +239,11 @@ class ApiService {
|
||||||
bool get isConnected => _channel != null && _channel!.closeCode == null;
|
bool get isConnected => _channel != null && _channel!.closeCode == null;
|
||||||
|
|
||||||
Future<void> _onDone() async {
|
Future<void> _onDone() async {
|
||||||
if (kDebugMode) {
|
|
||||||
print('API _onDone called');
|
|
||||||
}
|
|
||||||
_reconnectionDelay = 3;
|
_reconnectionDelay = 3;
|
||||||
await onClosed();
|
await onClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onError(dynamic e) async {
|
Future<void> _onError(dynamic e) async {
|
||||||
if (kDebugMode) {
|
|
||||||
print('API _onError called: $e');
|
|
||||||
}
|
|
||||||
if (e.toString().contains('Failed host lookup')) {
|
if (e.toString().contains('Failed host lookup')) {
|
||||||
Log.info('WebSocket connection failed: Host not reachable.');
|
Log.info('WebSocket connection failed: Host not reachable.');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -439,9 +418,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
if (res.error == ErrorCode.UserIdNotFound && contactId != null) {
|
if (res.error == ErrorCode.UserIdNotFound && contactId != null) {
|
||||||
Log.warn('Contact deleted their account $contactId.');
|
Log.warn('Contact deleted their account $contactId.');
|
||||||
final contact = await twonlyDB.contactsDao
|
final contact = await twonlyDB.contactsDao.getContactByUserId(contactId).getSingleOrNull();
|
||||||
.getContactByUserId(contactId)
|
|
||||||
.getSingleOrNull();
|
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
await twonlyDB.contactsDao.updateContact(
|
await twonlyDB.contactsDao.updateContact(
|
||||||
contactId,
|
contactId,
|
||||||
|
|
@ -506,8 +483,7 @@ class ApiService {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (result.isError) {
|
if (result.isError) {
|
||||||
if (result.error != ErrorCode.AuthTokenNotValid &&
|
if (result.error != ErrorCode.AuthTokenNotValid && result.error != ErrorCode.ForegroundSessionConnected) {
|
||||||
result.error != ErrorCode.ForegroundSessionConnected) {
|
|
||||||
Log.error(
|
Log.error(
|
||||||
'got error while authenticating to the server: ${result.error}',
|
'got error while authenticating to the server: ${result.error}',
|
||||||
);
|
);
|
||||||
|
|
@ -545,8 +521,7 @@ class ApiService {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (result.isError) {
|
if (result.isError) {
|
||||||
if (result.error != ErrorCode.AuthTokenNotValid &&
|
if (result.error != ErrorCode.AuthTokenNotValid && result.error != ErrorCode.ForegroundSessionConnected) {
|
||||||
result.error != ErrorCode.ForegroundSessionConnected) {
|
|
||||||
Log.error(
|
Log.error(
|
||||||
'got error while authenticating to the server: ${result.error}',
|
'got error while authenticating to the server: ${result.error}',
|
||||||
);
|
);
|
||||||
|
|
@ -578,8 +553,7 @@ class ApiService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final handshake = Handshake()
|
final handshake = Handshake()..getAuthChallenge = Handshake_GetAuthChallenge();
|
||||||
..getAuthChallenge = Handshake_GetAuthChallenge();
|
|
||||||
final req = createClientToServerFromHandshake(handshake);
|
final req = createClientToServerFromHandshake(handshake);
|
||||||
|
|
||||||
final result = await sendRequestSync(req, authenticated: false);
|
final result = await sendRequestSync(req, authenticated: false);
|
||||||
|
|
@ -644,9 +618,7 @@ class ApiService {
|
||||||
|
|
||||||
final register = Handshake_Register()
|
final register = Handshake_Register()
|
||||||
..username = username
|
..username = username
|
||||||
..publicIdentityKey = (await signalStore.getIdentityKeyPair())
|
..publicIdentityKey = (await signalStore.getIdentityKeyPair()).getPublicKey().serialize()
|
||||||
.getPublicKey()
|
|
||||||
.serialize()
|
|
||||||
..registrationId = Int64(signalIdentity.registrationId)
|
..registrationId = Int64(signalIdentity.registrationId)
|
||||||
..signedPrekey = signedPreKey.getKeyPair().publicKey.serialize()
|
..signedPrekey = signedPreKey.getKeyPair().publicKey.serialize()
|
||||||
..signedPrekeySignature = signedPreKey.signature
|
..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 {
|
Logger.root.onRecord.listen((record) async {
|
||||||
unawaited(_writeLogToFile(record));
|
unawaited(_writeLogToFile(record));
|
||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
// ignore: avoid_print
|
if (!Platform.environment.containsKey('FLUTTER_TEST') ||
|
||||||
print(
|
record.level >= Level.WARNING) {
|
||||||
'${record.level.name} [${AppState.isInBackgroundTask ? 'b' : 'f'}] [twonly] ${record.loggerName} > ${record.message}',
|
// 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: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/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
|
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
|
||||||
import 'package:twonly/src/visual/themes/light.dart';
|
import 'package:twonly/src/visual/themes/light.dart';
|
||||||
|
|
@ -23,16 +25,18 @@ class VerificationBadgeInfo extends StatelessWidget {
|
||||||
description: context.lang.verificationBadgeGreenDesc,
|
description: context.lang.verificationBadgeGreenDesc,
|
||||||
boldTextColor: primaryColor,
|
boldTextColor: primaryColor,
|
||||||
),
|
),
|
||||||
_buildItem(
|
if (userService.currentUser.securityProfile != SecurityProfile.strict ||
|
||||||
context,
|
userService.currentUser.isUserDiscoveryEnabled)
|
||||||
icon: const SvgIcon(
|
_buildItem(
|
||||||
assetPath: SvgIcons.verifiedGreen,
|
context,
|
||||||
size: 40,
|
icon: const SvgIcon(
|
||||||
color: colorVerificationBadgeYellow,
|
assetPath: SvgIcons.verifiedGreen,
|
||||||
|
size: 40,
|
||||||
|
color: colorVerificationBadgeYellow,
|
||||||
|
),
|
||||||
|
description: context.lang.verificationBadgeYellowDesc,
|
||||||
|
boldTextColor: colorVerificationBadgeYellow,
|
||||||
),
|
),
|
||||||
description: context.lang.verificationBadgeYellowDesc,
|
|
||||||
boldTextColor: colorVerificationBadgeYellow,
|
|
||||||
),
|
|
||||||
_buildItem(
|
_buildItem(
|
||||||
context,
|
context,
|
||||||
icon: const SvgIcon(assetPath: SvgIcons.verifiedRed, size: 40),
|
icon: const SvgIcon(assetPath: SvgIcons.verifiedRed, size: 40),
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,7 @@ class CameraScannedOverlay extends StatelessWidget {
|
||||||
...mainController.contactsVerified.values.map(
|
...mainController.contactsVerified.values.map(
|
||||||
(c) => _buildVerifiedContactTile(context, c),
|
(c) => _buildVerifiedContactTile(context, c),
|
||||||
),
|
),
|
||||||
if (mainController.scannedUrl != null)
|
if (mainController.scannedUrl != null) _buildScannedUrlTile(context, mainController.scannedUrl!),
|
||||||
_buildScannedUrlTile(context, mainController.scannedUrl!),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -46,15 +45,14 @@ class CameraScannedOverlay extends StatelessWidget {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
c.isLoading = true;
|
c.isLoading = true;
|
||||||
mainController.setState();
|
mainController.setState?.call();
|
||||||
|
|
||||||
showSnackbar(
|
showSnackbar(
|
||||||
context,
|
context,
|
||||||
context.lang.requestedUserToastText(c.profile.username),
|
context.lang.requestedUserToastText(c.profile.username),
|
||||||
level: SnackbarLevel.success,
|
level: SnackbarLevel.success,
|
||||||
);
|
);
|
||||||
if (await addNewContactFromPublicProfile(c.profile) &&
|
if (await addNewContactFromPublicProfile(c.profile) && context.mounted) {
|
||||||
context.mounted) {
|
|
||||||
// showSnackbar(
|
// showSnackbar(
|
||||||
// context,
|
// context,
|
||||||
// context.lang.requestedUserToastText(c.profile.username),
|
// context.lang.requestedUserToastText(c.profile.username),
|
||||||
|
|
@ -121,13 +119,11 @@ class CameraScannedOverlay extends StatelessWidget {
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 30,
|
width: 30,
|
||||||
child: Lottie.asset(
|
child: Lottie.asset(
|
||||||
c.verificationOk
|
c.verificationOk ? 'assets/animations/success.lottie' : 'assets/animations/failed.lottie',
|
||||||
? 'assets/animations/success.lottie'
|
|
||||||
: 'assets/animations/failed.lottie',
|
|
||||||
repeat: false,
|
repeat: false,
|
||||||
onLoaded: (p0) {
|
onLoaded: (p0) {
|
||||||
Future.delayed(const Duration(seconds: 4), () {
|
Future.delayed(const Duration(seconds: 4), () {
|
||||||
mainController.setState();
|
mainController.setState?.call();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class ScannedNewProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainCameraController {
|
class MainCameraController {
|
||||||
late void Function() setState;
|
void Function()? setState;
|
||||||
CameraController? cameraController;
|
CameraController? cameraController;
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails();
|
SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails();
|
||||||
|
|
@ -61,12 +61,12 @@ class MainCameraController {
|
||||||
|
|
||||||
void setSharedLinkForPreview(Uri? url) {
|
void setSharedLinkForPreview(Uri? url) {
|
||||||
sharedLinkForPreview = url;
|
sharedLinkForPreview = url;
|
||||||
setState();
|
setState?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onImageSend() {
|
void onImageSend() {
|
||||||
scannedUrl = '';
|
scannedUrl = '';
|
||||||
setState();
|
setState?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
final BarcodeScanner _barcodeScanner = BarcodeScanner();
|
final BarcodeScanner _barcodeScanner = BarcodeScanner();
|
||||||
|
|
@ -115,6 +115,7 @@ class MainCameraController {
|
||||||
);
|
);
|
||||||
initCameraStarted = false;
|
initCameraStarted = false;
|
||||||
selectedCameraDetails = SelectedCameraDetails();
|
selectedCameraDetails = SelectedCameraDetails();
|
||||||
|
setState?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> selectCamera(int sCameraId, bool init) async {
|
Future<void> selectCamera(int sCameraId, bool init) async {
|
||||||
|
|
@ -153,8 +154,11 @@ class MainCameraController {
|
||||||
try {
|
try {
|
||||||
_initializeFuture = cameraController?.initialize();
|
_initializeFuture = cameraController?.initialize();
|
||||||
await _initializeFuture;
|
await _initializeFuture;
|
||||||
|
if (cameraController == null) return;
|
||||||
await cameraController?.startImageStream(_processCameraImage);
|
await cameraController?.startImageStream(_processCameraImage);
|
||||||
|
if (cameraController == null) return;
|
||||||
await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor);
|
await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor);
|
||||||
|
if (cameraController == null) return;
|
||||||
if (userService.currentUser.videoStabilizationEnabled && !kDebugMode) {
|
if (userService.currentUser.videoStabilizationEnabled && !kDebugMode) {
|
||||||
await cameraController?.setVideoStabilizationMode(
|
await cameraController?.setVideoStabilizationMode(
|
||||||
VideoStabilizationMode.level1,
|
VideoStabilizationMode.level1,
|
||||||
|
|
@ -172,10 +176,13 @@ class MainCameraController {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.info(e);
|
Log.info(e);
|
||||||
}
|
}
|
||||||
|
if (cameraController == null) return;
|
||||||
selectedCameraDetails.scaleFactor = 1;
|
selectedCameraDetails.scaleFactor = 1;
|
||||||
|
|
||||||
await cameraController?.setZoomLevel(1);
|
await cameraController?.setZoomLevel(1);
|
||||||
|
if (cameraController == null) return;
|
||||||
await cameraController?.setDescription(AppEnvironment.cameras[cameraId]);
|
await cameraController?.setDescription(AppEnvironment.cameras[cameraId]);
|
||||||
|
if (cameraController == null) return;
|
||||||
try {
|
try {
|
||||||
if (!isVideoRecording) {
|
if (!isVideoRecording) {
|
||||||
await cameraController?.startImageStream(_processCameraImage);
|
await cameraController?.startImageStream(_processCameraImage);
|
||||||
|
|
@ -186,12 +193,15 @@ class MainCameraController {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (cameraController == null) return;
|
||||||
await cameraController?.lockCaptureOrientation(
|
await cameraController?.lockCaptureOrientation(
|
||||||
DeviceOrientation.portraitUp,
|
DeviceOrientation.portraitUp,
|
||||||
);
|
);
|
||||||
|
if (cameraController == null) return;
|
||||||
await cameraController?.setFlashMode(
|
await cameraController?.setFlashMode(
|
||||||
selectedCameraDetails.isFlashOn ? FlashMode.always : FlashMode.off,
|
selectedCameraDetails.isFlashOn ? FlashMode.always : FlashMode.off,
|
||||||
);
|
);
|
||||||
|
if (cameraController == null) return;
|
||||||
selectedCameraDetails.maxAvailableZoom = await cameraController?.getMaxZoomLevel() ?? 1;
|
selectedCameraDetails.maxAvailableZoom = await cameraController?.getMaxZoomLevel() ?? 1;
|
||||||
selectedCameraDetails.minAvailableZoom = await cameraController?.getMinZoomLevel() ?? 1;
|
selectedCameraDetails.minAvailableZoom = await cameraController?.getMinZoomLevel() ?? 1;
|
||||||
selectedCameraDetails
|
selectedCameraDetails
|
||||||
|
|
@ -204,7 +214,7 @@ class MainCameraController {
|
||||||
isSelectingFaceFilters = false;
|
isSelectingFaceFilters = false;
|
||||||
setFilter(FaceFilterType.none);
|
setFilter(FaceFilterType.none);
|
||||||
zoomButtonKey = GlobalKey();
|
zoomButtonKey = GlobalKey();
|
||||||
setState();
|
setState?.call();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
cameraController = null;
|
cameraController = null;
|
||||||
|
|
@ -226,7 +236,7 @@ class MainCameraController {
|
||||||
final dx = (localPosition.dx / box.size.width).clamp(0.0, 1.0);
|
final dx = (localPosition.dx / box.size.width).clamp(0.0, 1.0);
|
||||||
final dy = (localPosition.dy / box.size.height).clamp(0.0, 1.0);
|
final dy = (localPosition.dy / box.size.height).clamp(0.0, 1.0);
|
||||||
|
|
||||||
setState();
|
setState?.call();
|
||||||
|
|
||||||
await HapticFeedback.lightImpact();
|
await HapticFeedback.lightImpact();
|
||||||
try {
|
try {
|
||||||
|
|
@ -244,7 +254,7 @@ class MainCameraController {
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
focusPointOffset = null;
|
focusPointOffset = null;
|
||||||
setState();
|
setState?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setFilter(FaceFilterType type) {
|
void setFilter(FaceFilterType type) {
|
||||||
|
|
@ -254,7 +264,7 @@ class MainCameraController {
|
||||||
facePaint = null;
|
facePaint = null;
|
||||||
_isBusyFaces = false;
|
_isBusyFaces = false;
|
||||||
}
|
}
|
||||||
setState();
|
setState?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
FaceFilterPainter? faceFilterPainter;
|
FaceFilterPainter? faceFilterPainter;
|
||||||
|
|
@ -419,7 +429,7 @@ class MainCameraController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_isBusy = false;
|
_isBusy = false;
|
||||||
setState();
|
setState?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _processFaces(InputImage inputImage) async {
|
Future<void> _processFaces(InputImage inputImage) async {
|
||||||
|
|
@ -465,6 +475,6 @@ class MainCameraController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_isBusyFaces = false;
|
_isBusyFaces = false;
|
||||||
setState();
|
setState?.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_mainCameraController.setState = null;
|
||||||
_mainCameraController.closeCamera();
|
_mainCameraController.closeCamera();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_mainCameraController.setState = null;
|
||||||
_mainCameraController.closeCamera();
|
_mainCameraController.closeCamera();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
List<Group> _groupsPinned = [];
|
List<Group> _groupsPinned = [];
|
||||||
List<Group> _groupsArchived = [];
|
List<Group> _groupsArchived = [];
|
||||||
|
|
||||||
bool _hasContacts = true;
|
bool _hasContacts = false;
|
||||||
bool get _hasOpenGroup => _groupsNotPinned.isNotEmpty || _groupsArchived.isNotEmpty || _groupsPinned.isNotEmpty;
|
bool get _hasOpenGroup => _groupsNotPinned.isNotEmpty || _groupsArchived.isNotEmpty || _groupsPinned.isNotEmpty;
|
||||||
|
|
||||||
GlobalKey searchForOtherUsers = GlobalKey();
|
GlobalKey searchForOtherUsers = GlobalKey();
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ class HomeViewState extends State<HomeView> {
|
||||||
_homeViewPageIndexSub?.cancel();
|
_homeViewPageIndexSub?.cancel();
|
||||||
_selectNotificationSub?.cancel();
|
_selectNotificationSub?.cancel();
|
||||||
_disableCameraTimer?.cancel();
|
_disableCameraTimer?.cancel();
|
||||||
|
_mainCameraController.setState = null;
|
||||||
_mainCameraController.closeCamera();
|
_mainCameraController.closeCamera();
|
||||||
_intentStreamSub?.cancel();
|
_intentStreamSub?.cancel();
|
||||||
_deepLinkSub?.cancel();
|
_deepLinkSub?.cancel();
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,8 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
displayName: username,
|
displayName: username,
|
||||||
subscriptionPlan: 'Free',
|
subscriptionPlan: 'Free',
|
||||||
currentSetupPage: SetupPages.profile.name,
|
currentSetupPage: SetupPages.profile.name,
|
||||||
)..appVersion = AppState.latestAppVersionId;
|
appVersion: AppState.latestAppVersionId,
|
||||||
|
);
|
||||||
|
|
||||||
await UserService.save(userData);
|
await UserService.save(userData);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,14 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/locator.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/services/user.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.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/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/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.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/share_your_friends.setup.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup/verification_badge.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';
|
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 {
|
enum SetupPages {
|
||||||
profile,
|
profile,
|
||||||
backup,
|
backup,
|
||||||
|
profileSelection,
|
||||||
|
securityProfile,
|
||||||
verificationBadge,
|
verificationBadge,
|
||||||
shareYourFriends,
|
shareYourFriends,
|
||||||
letYourFriendsFindYou,
|
letYourFriendsFindYou,
|
||||||
|
|
@ -27,25 +32,62 @@ extension SetupPagesExtension on SetupPages {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int get pageNumber => index + 1;
|
static List<SetupPages> get activePages {
|
||||||
int get totalPages => SetupPages.values.length;
|
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();
|
int get progressPercentage => ((pageNumber - 1) / totalPages * 100).round();
|
||||||
String get progressText => '$pageNumber / $totalPages';
|
String get progressText => '$pageNumber / $totalPages';
|
||||||
|
|
||||||
bool get isLast => index == SetupPages.values.length - 1;
|
bool get isLast {
|
||||||
|
return activePages.isNotEmpty && activePages.last == this;
|
||||||
|
}
|
||||||
|
|
||||||
SetupPages? next() {
|
SetupPages? next() {
|
||||||
final nextIndex = index + 1;
|
final pages = activePages;
|
||||||
if (nextIndex < SetupPages.values.length) {
|
final idx = pages.indexOf(this);
|
||||||
return SetupPages.values[nextIndex];
|
if (idx != -1 && idx + 1 < pages.length) {
|
||||||
|
return pages[idx + 1];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetupPages? previous() {
|
SetupPages? previous() {
|
||||||
final prevIndex = index - 1;
|
final pages = activePages;
|
||||||
if (prevIndex >= 0) {
|
final idx = pages.indexOf(this);
|
||||||
return SetupPages.values[prevIndex];
|
if (idx > 0) {
|
||||||
|
return pages[idx - 1];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -110,9 +152,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
right: index == currentPage.totalPages - 1 ? 0 : 8,
|
right: index == currentPage.totalPages - 1 ? 0 : 8,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isFinished
|
color: isFinished ? context.color.primary : context.color.surfaceContainer,
|
||||||
? context.color.primary
|
|
||||||
: context.color.surfaceContainer,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -149,8 +189,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (currentPage.index > 0 && !currentPage.isLast)
|
if (currentPage.index > 0 && !currentPage.isLast) const SizedBox(width: 24),
|
||||||
const SizedBox(width: 24),
|
|
||||||
if (!currentPage.isLast)
|
if (!currentPage.isLast)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|
@ -183,6 +222,10 @@ class _SetupViewState extends State<SetupView> {
|
||||||
return const ProfileSetupPage();
|
return const ProfileSetupPage();
|
||||||
case SetupPages.backup:
|
case SetupPages.backup:
|
||||||
return const BackupSetupPage();
|
return const BackupSetupPage();
|
||||||
|
case SetupPages.profileSelection:
|
||||||
|
return const ProfileSelectionSetup();
|
||||||
|
case SetupPages.securityProfile:
|
||||||
|
return const SecurityProfileSetup();
|
||||||
case SetupPages.verificationBadge:
|
case SetupPages.verificationBadge:
|
||||||
return const VerificationBadgeSetupPage();
|
return const VerificationBadgeSetupPage();
|
||||||
case SetupPages.shareYourFriends:
|
case SetupPages.shareYourFriends:
|
||||||
|
|
|
||||||
|
|
@ -18,43 +18,48 @@ class NextButtonComp extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final currentPage = SetupPagesExtension.fromStr(
|
return StreamBuilder<void>(
|
||||||
userService.currentUser.currentSetupPage,
|
stream: userService.onUserUpdated,
|
||||||
);
|
builder: (context, snapshot) {
|
||||||
return ElevatedButton(
|
final currentPage = SetupPagesExtension.fromStr(
|
||||||
onPressed: (canSubmit && !isLoading)
|
userService.currentUser.currentSetupPage,
|
||||||
? () async {
|
);
|
||||||
if (onPressed != null) {
|
return ElevatedButton(
|
||||||
final error = await onPressed?.call();
|
onPressed: (canSubmit && !isLoading)
|
||||||
if (error == true) return;
|
? () async {
|
||||||
}
|
if (onPressed != null) {
|
||||||
await UserService.update((user) {
|
final error = await onPressed?.call();
|
||||||
user.currentSetupPage = currentPage.next()?.name;
|
if (error == true) return;
|
||||||
});
|
}
|
||||||
}
|
await UserService.update((user) {
|
||||||
: null,
|
user.currentSetupPage = currentPage.next()?.name;
|
||||||
style: ElevatedButton.styleFrom(
|
});
|
||||||
minimumSize: const Size(double.infinity, 56),
|
}
|
||||||
backgroundColor: context.color.primary,
|
: null,
|
||||||
foregroundColor: context.color.onPrimary,
|
style: ElevatedButton.styleFrom(
|
||||||
elevation: 0,
|
minimumSize: const Size(double.infinity, 56),
|
||||||
shape: RoundedRectangleBorder(
|
backgroundColor: context.color.primary,
|
||||||
borderRadius: BorderRadius.circular(16),
|
foregroundColor: context.color.onPrimary,
|
||||||
),
|
elevation: 0,
|
||||||
),
|
shape: RoundedRectangleBorder(
|
||||||
child: isLoading
|
borderRadius: BorderRadius.circular(16),
|
||||||
? 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),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
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:go_router/go_router.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.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/services/user.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
|
@ -78,6 +79,18 @@ class _PrivacyViewState extends State<PrivacyView> {
|
||||||
setState(() {});
|
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(),
|
const Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(context.lang.settingsTypingIndication),
|
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 {
|
class UserDiscoverySetupState {
|
||||||
UserDiscoverySetupState({
|
UserDiscoverySetupState({
|
||||||
required this.setState,
|
this.setState,
|
||||||
this.isUserDiscoveryEnabled = true,
|
this.isUserDiscoveryEnabled = true,
|
||||||
this.sharePromotion = true,
|
this.sharePromotion = true,
|
||||||
this.isManualApprovalEnabled = false,
|
this.isManualApprovalEnabled = false,
|
||||||
|
|
@ -43,13 +43,17 @@ class UserDiscoverySetupState {
|
||||||
bool isManualApprovalEnabled;
|
bool isManualApprovalEnabled;
|
||||||
int requiredSendImages;
|
int requiredSendImages;
|
||||||
|
|
||||||
void Function(void Function()) setState;
|
void Function(void Function())? setState;
|
||||||
|
|
||||||
void update(void Function() update) {
|
void update(void Function() update) {
|
||||||
update();
|
update();
|
||||||
setState(() {
|
if (setState != null) {
|
||||||
|
setState!(() {
|
||||||
|
wasChanged = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
wasChanged = true;
|
wasChanged = true;
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> initializeOrUpdate() async {
|
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!
|
// 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()
|
let registry = Registry::default()
|
||||||
.with(
|
.with(
|
||||||
EnvFilter::try_from_default_env()
|
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);
|
.with(stdout_layer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,8 @@ void main() {
|
||||||
displayName: 'Test User',
|
displayName: 'Test User',
|
||||||
subscriptionPlan: 'Free',
|
subscriptionPlan: 'Free',
|
||||||
currentSetupPage: null,
|
currentSetupPage: null,
|
||||||
)..appVersion = 62;
|
appVersion: 62,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test flame counter', () async {
|
test('test flame counter', () async {
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,6 @@ void setupPlatformChannelMocks() {
|
||||||
Future<dynamic> mockHandler(MethodCall methodCall) async {
|
Future<dynamic> mockHandler(MethodCall methodCall) async {
|
||||||
final userId = Zone.current[#userId] as int?;
|
final userId = Zone.current[#userId] as int?;
|
||||||
final keyPrefix = userId != null ? '${userId}_' : '';
|
final keyPrefix = userId != null ? '${userId}_' : '';
|
||||||
print(
|
|
||||||
'DEBUG: mockHandler: method=${methodCall.method}, key=${methodCall.arguments?['key']}, userId=$userId, prefix=$keyPrefix',
|
|
||||||
);
|
|
||||||
if (methodCall.method == 'read') {
|
if (methodCall.method == 'read') {
|
||||||
final key = methodCall.arguments['key'] as String;
|
final key = methodCall.arguments['key'] as String;
|
||||||
return secureStorageMock[keyPrefix + key];
|
return secureStorageMock[keyPrefix + key];
|
||||||
|
|
@ -43,43 +40,38 @@ void setupPlatformChannelMocks() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
.setMockMethodCallHandler(
|
const MethodChannel(
|
||||||
const MethodChannel(
|
'plugins.it_crowd.double_tapp/flutter_secure_storage',
|
||||||
'plugins.it_crowd.double_tapp/flutter_secure_storage',
|
),
|
||||||
),
|
mockHandler,
|
||||||
mockHandler,
|
);
|
||||||
);
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'),
|
||||||
.setMockMethodCallHandler(
|
mockHandler,
|
||||||
const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'),
|
);
|
||||||
mockHandler,
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
);
|
const MethodChannel('dev.fluttercommunity.plus/connectivity'),
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
(call) async {
|
||||||
.setMockMethodCallHandler(
|
if (call.method == 'check') {
|
||||||
const MethodChannel('dev.fluttercommunity.plus/connectivity'),
|
return ['wifi'];
|
||||||
(call) async {
|
}
|
||||||
if (call.method == 'check') {
|
return null;
|
||||||
return ['wifi'];
|
},
|
||||||
}
|
);
|
||||||
return null;
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
},
|
const MethodChannel(
|
||||||
);
|
'be.tramesch.workmanager/foreground_channel_workmanager',
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
),
|
||||||
.setMockMethodCallHandler(
|
(call) async => true,
|
||||||
const MethodChannel(
|
);
|
||||||
'be.tramesch.workmanager/foreground_channel_workmanager',
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
),
|
const MethodChannel('com.bbflight.background_downloader'),
|
||||||
(call) async => true,
|
(call) async {
|
||||||
);
|
if (call.method == 'enqueue') {
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
return true;
|
||||||
.setMockMethodCallHandler(
|
}
|
||||||
const MethodChannel('com.bbflight.background_downloader'),
|
return null;
|
||||||
(call) async {
|
},
|
||||||
if (call.method == 'enqueue') {
|
);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,8 @@ class TestClient {
|
||||||
displayName: username,
|
displayName: username,
|
||||||
subscriptionPlan: 'Free',
|
subscriptionPlan: 'Free',
|
||||||
currentSetupPage: null,
|
currentSetupPage: null,
|
||||||
|
appVersion: 100,
|
||||||
);
|
);
|
||||||
// ignore: cascade_invocations
|
|
||||||
userData.appVersion = 100;
|
|
||||||
await UserService.save(userData);
|
await UserService.save(userData);
|
||||||
|
|
||||||
await api.authenticate();
|
await api.authenticate();
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,8 @@ class UserEnvironment {
|
||||||
displayName: '$username Display',
|
displayName: '$username Display',
|
||||||
subscriptionPlan: 'Free',
|
subscriptionPlan: 'Free',
|
||||||
currentSetupPage: null,
|
currentSetupPage: null,
|
||||||
)..appVersion = 100;
|
appVersion: 100,
|
||||||
|
);
|
||||||
|
|
||||||
// ignore: cascade_invocations
|
// ignore: cascade_invocations
|
||||||
us.isUserCreated = true;
|
us.isUserCreated = true;
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,8 @@ void main() {
|
||||||
displayName: 'Test User',
|
displayName: 'Test User',
|
||||||
subscriptionPlan: 'Free',
|
subscriptionPlan: 'Free',
|
||||||
currentSetupPage: null,
|
currentSetupPage: null,
|
||||||
)..appVersion = 100;
|
appVersion: 100,
|
||||||
|
);
|
||||||
userService.isUserCreated = true;
|
userService.isUserCreated = true;
|
||||||
await UserService.save(userService.currentUser);
|
await UserService.save(userService.currentUser);
|
||||||
initialUserData = (await KeyValueStore.get('user'))!;
|
initialUserData = (await KeyValueStore.get('user'))!;
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,8 @@ void main() {
|
||||||
displayName: 'Test User',
|
displayName: 'Test User',
|
||||||
subscriptionPlan: 'Free',
|
subscriptionPlan: 'Free',
|
||||||
currentSetupPage: null,
|
currentSetupPage: null,
|
||||||
)..appVersion = 100;
|
appVersion: 100,
|
||||||
|
);
|
||||||
userService.isUserCreated = true;
|
userService.isUserCreated = true;
|
||||||
AppEnvironment.initTesting();
|
AppEnvironment.initTesting();
|
||||||
// Log.init();
|
// Log.init();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue