mirror of
https://gitlab.com/comunic/comunicmobile
synced 2025-07-01 06:03:29 +00:00
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
1ee9a2c5cc | |||
25a25e4c70 | |||
d4a0748249 | |||
1ea286f3ef | |||
a32e968992 | |||
0cd9371460 | |||
1b0a3fd24b | |||
54e37b3e69 | |||
2519adeef4 | |||
4f9001cb2b | |||
613ceadfaa | |||
459757b292 | |||
e399f71a78 | |||
c5d1512375 | |||
16ec9a8e00 | |||
c19cbaac88 | |||
277c08048d | |||
482e938744 | |||
1d0bd45632 | |||
3a39387365 | |||
4d885affb9 | |||
0ad8d5c393 | |||
e5ed4fadda | |||
581059cb1d | |||
0c526abfe8 |
16
Makefile
Normal file
16
Makefile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
beta_online_release:
|
||||||
|
flutter build apk --flavor beta -t lib/main_online.dart
|
||||||
|
|
||||||
|
|
||||||
|
beta_online_release_split_per_abi:
|
||||||
|
flutter build apk --flavor beta -t lib/main_online.dart --target-platform android-arm,android-arm64,android-x64 --split-per-abi
|
||||||
|
|
||||||
|
|
||||||
|
stable_release_split_per_abi:
|
||||||
|
flutter build apk --flavor stable -t lib/main_online.dart --target-platform android-arm,android-arm64,android-x64 --split-per-abi
|
||||||
|
|
||||||
|
stable_release:
|
||||||
|
flutter build apk --flavor stable -t lib/main_online.dart
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: beta_offline_release beta_online_release_split_per_abi stable_release_split_per_abi
|
@ -13,9 +13,18 @@
|
|||||||
"1 member": "1 membre",
|
"1 member": "1 membre",
|
||||||
"1 month": "1 mois",
|
"1 month": "1 mois",
|
||||||
"1 year": "1 an",
|
"1 year": "1 an",
|
||||||
|
"10 years": "10 ans",
|
||||||
|
"15 days": "15 jours",
|
||||||
|
"2 years": "2 ans",
|
||||||
|
"3 months": "3 mois",
|
||||||
|
"5 years": "5 ans",
|
||||||
|
"50 years": "50 ans",
|
||||||
|
"6 months": "6 mois",
|
||||||
|
"7 days": "7 jours",
|
||||||
":yourShortcut:": ":votreRaccourcis:",
|
":yourShortcut:": ":votreRaccourcis:",
|
||||||
"A network error occured!": "Une erreur de réseau s'est produite !",
|
"A network error occured!": "Une erreur de réseau s'est produite !",
|
||||||
"A registration is required to access this group page.": "Une inscription est nécessaire pour accéder à cette page de groupe !",
|
"A registration is required to access this group page.": "Une inscription est nécessaire pour accéder à cette page de groupe !",
|
||||||
|
"ALL": "TOUS",
|
||||||
"About this application": "A propos de cette application",
|
"About this application": "A propos de cette application",
|
||||||
"Accept": "Accepter",
|
"Accept": "Accepter",
|
||||||
"Accept request": "Accepter la demande",
|
"Accept request": "Accepter la demande",
|
||||||
@ -41,6 +50,7 @@
|
|||||||
"An error occured while creating your account. Please try again.": "Une erreur s'est produite lors de la création du compte. Veuillez ré-essayer.",
|
"An error occured while creating your account. Please try again.": "Une erreur s'est produite lors de la création du compte. Veuillez ré-essayer.",
|
||||||
"An error occurred while checking your options !": "Erreur lors de la récupération de vos options de récupération !",
|
"An error occurred while checking your options !": "Erreur lors de la récupération de vos options de récupération !",
|
||||||
"An error occurred while checking your recovery options !": "Erreur lors de la récupération de vos options de récupération !",
|
"An error occurred while checking your recovery options !": "Erreur lors de la récupération de vos options de récupération !",
|
||||||
|
"An error occurred while creating your account. Please try again.": "Erreur lors de la création de votre compte. Veuillez réessayer",
|
||||||
"Answer %num%": "Réponse %num%",
|
"Answer %num%": "Réponse %num%",
|
||||||
"Answer 1": "Réponse 1",
|
"Answer 1": "Réponse 1",
|
||||||
"Answer 2": "Réponse 2",
|
"Answer 2": "Réponse 2",
|
||||||
@ -49,6 +59,12 @@
|
|||||||
"Appearance": "Apparence",
|
"Appearance": "Apparence",
|
||||||
"Application settings": "Paramètres de l'application",
|
"Application settings": "Paramètres de l'application",
|
||||||
"Are you sure do you want to remove this friend from your list of friends ? A friendship request will have to be sent to get this user back to your list!": "Voulez-vous vraiment supprimer cet ami de votre liste d'amis ? Il faudra une demande d'ami pour réintégrer cet utilisateur à votre liste !",
|
"Are you sure do you want to remove this friend from your list of friends ? A friendship request will have to be sent to get this user back to your list!": "Voulez-vous vraiment supprimer cet ami de votre liste d'amis ? Il faudra une demande d'ami pour réintégrer cet utilisateur à votre liste !",
|
||||||
|
"Automatically delete unread notifications after": "Supprimer automatiquement les notifications non lues après",
|
||||||
|
"Automatically delete your account if you have been inactive for": "Supprimer votre compte si vous avez été déconnecté pendant",
|
||||||
|
"Automatically delete your comments after": "Supprimer automatiquement vos commentaires après",
|
||||||
|
"Automatically delete your conversation messages after": "Supprimer automatiqumenet vos messages de conversations après",
|
||||||
|
"Automatically delete your likes after": "Supprimer automatiquement vos \"J'aime\" après",
|
||||||
|
"Automatically delete your posts after": "Supprimer automatiquement vos posts après",
|
||||||
"Block the creation of new responses": "Bloquer la création de nouvelles réponses",
|
"Block the creation of new responses": "Bloquer la création de nouvelles réponses",
|
||||||
"Camera": "Caméra",
|
"Camera": "Caméra",
|
||||||
"Can access to all group posts": "Peut accéder à tous les posts du groupe",
|
"Can access to all group posts": "Peut accéder à tous les posts du groupe",
|
||||||
@ -181,6 +197,7 @@
|
|||||||
"Custom emojis": "Emoticons personnalisés",
|
"Custom emojis": "Emoticons personnalisés",
|
||||||
"Customize your account image": "Personalisez votre image de compte",
|
"Customize your account image": "Personalisez votre image de compte",
|
||||||
"Danger zone": "Zone de danger",
|
"Danger zone": "Zone de danger",
|
||||||
|
"Data conservation policy": "Politique de conservation des données",
|
||||||
"Debug features": "Fonctionnalités de développement",
|
"Debug features": "Fonctionnalités de développement",
|
||||||
"Delete": "Supprimer",
|
"Delete": "Supprimer",
|
||||||
"Delete account image": "Supprimer l'image de compte",
|
"Delete account image": "Supprimer l'image de compte",
|
||||||
@ -190,6 +207,7 @@
|
|||||||
"Delete group": "Supprimer le groupe",
|
"Delete group": "Supprimer le groupe",
|
||||||
"Delete logo": "Supprimer le logo",
|
"Delete logo": "Supprimer le logo",
|
||||||
"Delete your account": "Supprimer votre compte",
|
"Delete your account": "Supprimer votre compte",
|
||||||
|
"Deprecated application version": "Version obsolète de l'application",
|
||||||
"Disconnect all your devices": "Déconnecter tous vos appareils",
|
"Disconnect all your devices": "Déconnecter tous vos appareils",
|
||||||
"Disconnect all your devices from Comunic, including the current one. Use this option if one of the device you use for Comunic was stolen.": "Déconnecte tous vos appareils de Comunic, en incluant l'appareil actuel. Nous vous recommandons d'utiliser cette option si vous avez des raisons de penser que l'un des appareils que vous utiliser pour accéder à Comunic a été volé.",
|
"Disconnect all your devices from Comunic, including the current one. Use this option if one of the device you use for Comunic was stolen.": "Déconnecte tous vos appareils de Comunic, en incluant l'appareil actuel. Nous vous recommandons d'utiliser cette option si vous avez des raisons de penser que l'un des appareils que vous utiliser pour accéder à Comunic a été volé.",
|
||||||
"Do you really want to block new choices creation?": "Voulez-vous vraiment bloquer la création de nouveaux choix ?",
|
"Do you really want to block new choices creation?": "Voulez-vous vraiment bloquer la création de nouveaux choix ?",
|
||||||
@ -213,6 +231,7 @@
|
|||||||
"Do you really want to remove this membership ?": "Voulez-vous vraiment supprimer cette inscription ?",
|
"Do you really want to remove this membership ?": "Voulez-vous vraiment supprimer cette inscription ?",
|
||||||
"Do you really want to sign out from the application ?": "Voulez-vous vraiment vous déconnecter de l'application ?",
|
"Do you really want to sign out from the application ?": "Voulez-vous vraiment vous déconnecter de l'application ?",
|
||||||
"Do you want to unselected currently selected image ?": "Voulez-vous désélectionner l'image ?",
|
"Do you want to unselected currently selected image ?": "Voulez-vous désélectionner l'image ?",
|
||||||
|
"Download update outside Play Store": "Télécharger la mise hors du Play Store",
|
||||||
"Email address": "Adresse e-mail",
|
"Email address": "Adresse e-mail",
|
||||||
"Email address...": "Adresse mail...",
|
"Email address...": "Adresse mail...",
|
||||||
"Enable dark theme": "Activer le thème sombre",
|
"Enable dark theme": "Activer le thème sombre",
|
||||||
@ -222,6 +241,8 @@
|
|||||||
"Everyone": "Tout le monde",
|
"Everyone": "Tout le monde",
|
||||||
"Everyone can choose to join the group without moderator approval": "Tout le monde peut rejoindre le groupe, sans l'approbation d'un modérateur",
|
"Everyone can choose to join the group without moderator approval": "Tout le monde peut rejoindre le groupe, sans l'approbation d'un modérateur",
|
||||||
"Everyone can request a membership, but a moderator review the request": "Tout le monde peut demander à rejoindre le groupe, mais un modérateur doit accepter les demandes",
|
"Everyone can request a membership, but a moderator review the request": "Tout le monde peut demander à rejoindre le groupe, mais un modérateur doit accepter les demandes",
|
||||||
|
"Failed to load privacy settings!": "Erreur lors du chargement des paramètres de vie privée !",
|
||||||
|
"Failed to update data conservation policy!": "Echec de la mise à jour des paramètres de vie privée !",
|
||||||
"First name": "Prénom",
|
"First name": "Prénom",
|
||||||
"Follow": "Suivre",
|
"Follow": "Suivre",
|
||||||
"Follow conversation": "Suivre la conversation",
|
"Follow conversation": "Suivre la conversation",
|
||||||
@ -229,7 +250,7 @@
|
|||||||
"Force mobile mode": "Forcer l'utilisation du mode mobile",
|
"Force mobile mode": "Forcer l'utilisation du mode mobile",
|
||||||
"Force the smartphone mode of the application to be used, even when tablet mode could be used.": "Forcer l'utilisation du mode smartphone de l'application, même lorsque le mode tablette est disponible.",
|
"Force the smartphone mode of the application to be used, even when tablet mode could be used.": "Forcer l'utilisation du mode smartphone de l'application, même lorsque le mode tablette est disponible.",
|
||||||
"Form can not be submitted at this point!": "Impossible de soumettre le formulaire à ce stade !",
|
"Form can not be submitted at this point!": "Impossible de soumettre le formulaire à ce stade !",
|
||||||
"Free social network that respect your privacy": "Réseau sociale libre qui respecte votre vie privée",
|
"Free social network that respect your privacy": "Réseau social libre qui respecte votre vie privée",
|
||||||
"Friends": "Amis",
|
"Friends": "Amis",
|
||||||
"Friends of %name%": "Amis de %name%",
|
"Friends of %name%": "Amis de %name%",
|
||||||
"Friends only": "Amis seulement",
|
"Friends only": "Amis seulement",
|
||||||
@ -238,6 +259,7 @@
|
|||||||
"General settings": "Paramètres généraux",
|
"General settings": "Paramètres généraux",
|
||||||
"Generate a new random logo": "Générer un logo aléatoire",
|
"Generate a new random logo": "Générer un logo aléatoire",
|
||||||
"Generate a random account image": "Générer une image de compte aléatoire",
|
"Generate a random account image": "Générer une image de compte aléatoire",
|
||||||
|
"Go to the Play Store": "Accéder au Play Store",
|
||||||
"Group": "Groupe",
|
"Group": "Groupe",
|
||||||
"Group ID": "Identifiant du gorupe",
|
"Group ID": "Identifiant du gorupe",
|
||||||
"Group URL (optional)": "URL du groupe (optionnelle)",
|
"Group URL (optional)": "URL du groupe (optionnelle)",
|
||||||
@ -293,6 +315,7 @@
|
|||||||
"My friends only": "Mes amis uniquement",
|
"My friends only": "Mes amis uniquement",
|
||||||
"Name of the group": "Nom du groupe",
|
"Name of the group": "Nom du groupe",
|
||||||
"Name of the group to create": "Nom du groupe à créer",
|
"Name of the group to create": "Nom du groupe à créer",
|
||||||
|
"Never": "Jamais",
|
||||||
"New choice": "Nouveau choix",
|
"New choice": "Nouveau choix",
|
||||||
"New choice...": "Nouveau choix...",
|
"New choice...": "Nouveau choix...",
|
||||||
"New comment...": "Nouveau commentaire...",
|
"New comment...": "Nouveau commentaire...",
|
||||||
@ -391,6 +414,7 @@
|
|||||||
"This account is private.": "Ce compte est privé.",
|
"This account is private.": "Ce compte est privé.",
|
||||||
"This kind of notification is not supported yet by this application.": "Ce type de notification n'est pas encore supportée par l'application.",
|
"This kind of notification is not supported yet by this application.": "Ce type de notification n'est pas encore supportée par l'application.",
|
||||||
"This password is not the same as the other one!": "Ce mot de passe est différent de l'autre",
|
"This password is not the same as the other one!": "Ce mot de passe est différent de l'autre",
|
||||||
|
"This version of the Comunic application is deprecated. You might still be able to use it, but some features may not work. We recommend you to update to the latest version of the application.": "Cette version de l'application Comunic est obsolète. Vous pouvez continuer à l'utiliser, mais certaines fonctionalités pourront ne plus fonctionner. Nous vous recommandons d'installer la dernière version de l'applicatioon.",
|
||||||
"This virtual directory is invalid / unvailable !": "Ce répertoire virtuel est invalide / indisponible !",
|
"This virtual directory is invalid / unvailable !": "Ce répertoire virtuel est invalide / indisponible !",
|
||||||
"Too many accounts have been created from this IP address for now. Please try again later.": "Trop de comptes ont été créés avec cette addresse IP pour l'instant. Veuillez ré-essayer plus tard.",
|
"Too many accounts have been created from this IP address for now. Please try again later.": "Trop de comptes ont été créés avec cette addresse IP pour l'instant. Veuillez ré-essayer plus tard.",
|
||||||
"Too many unsuccessfull login attempts! Please try again later...": "Trop de tentatives de connexion ont échoué. Veuillez ré-essayer plus tard...",
|
"Too many unsuccessfull login attempts! Please try again later...": "Trop de tentatives de connexion ont échoué. Veuillez ré-essayer plus tard...",
|
||||||
@ -407,7 +431,9 @@
|
|||||||
"Upload a new logo": "Envoyer un nouveau logo",
|
"Upload a new logo": "Envoyer un nouveau logo",
|
||||||
"Upload an account image": "Envoyer une nouvelle image de compte",
|
"Upload an account image": "Envoyer une nouvelle image de compte",
|
||||||
"Upload new account image": "Changer l'image de compte",
|
"Upload new account image": "Changer l'image de compte",
|
||||||
|
"Use the old application anyway": "Utiliser l'ancienne version",
|
||||||
"User ID": "Numéro d'utilisateur",
|
"User ID": "Numéro d'utilisateur",
|
||||||
|
"Version %version% - Build %build%": "Version %version% - Build %build%",
|
||||||
"Virtual directory": "Répertoire virtuel",
|
"Virtual directory": "Répertoire virtuel",
|
||||||
"Virtual directory (optional)": "Dossier virtuel (optionnel)",
|
"Virtual directory (optional)": "Dossier virtuel (optionnel)",
|
||||||
"Visitor": "Visiteur",
|
"Visitor": "Visiteur",
|
||||||
@ -430,6 +456,11 @@
|
|||||||
"Your new password": "Votre nouveau mot de passe",
|
"Your new password": "Votre nouveau mot de passe",
|
||||||
"Your page settings": "Paramètres de votre page",
|
"Your page settings": "Paramètres de votre page",
|
||||||
"Your password has been successfully changed!": "Votre mot de passe a été changé avec succès !",
|
"Your password has been successfully changed!": "Votre mot de passe a été changé avec succès !",
|
||||||
|
"Your password must be composed of at least %num% characters!": "Votre mot de passe doit être composé d'au moins %num% caractères !",
|
||||||
|
"Your password must contains characters of at least %num% of the following categories : %upper% upper case letter, %lower% lowercase letter, %digit% digit, %special% special character.": "Votre mot de passe doit contenir des caractères d'au moins %num% des catégories suivants : %upper% lettre majuscule, %lower% lettre minuscule, %digit% chiffre, %special% caractères spéciaux.",
|
||||||
|
"Your password must not contains part of your email address!": "Votre mot de passe ne doit pas contenir des parties de votre adresse mail !",
|
||||||
|
"Your password must not contains your first name!": "Votre mot de passe ne doit pas contenir votre prénom !",
|
||||||
|
"Your password must not contains your last name!": "Votre mot de passe ne doit pas contenir votre nom !",
|
||||||
"Your response: %response%": "Votre réponse : %response%",
|
"Your response: %response%": "Votre réponse : %response%",
|
||||||
"Your security questions can be used to recover an access to your account when you loose your password...": "Vos questions de sécurité peuvent être utilisées pour récupérer l'accès à votre compte lorsque vous perdez votre mot de passe...",
|
"Your security questions can be used to recover an access to your account when you loose your password...": "Vos questions de sécurité peuvent être utilisées pour récupérer l'accès à votre compte lorsque vous perdez votre mot de passe...",
|
||||||
"accepted his invitation to join the group": "a accepté son invitation à rejoindre le groupe",
|
"accepted his invitation to join the group": "a accepté son invitation à rejoindre le groupe",
|
||||||
|
@ -2,13 +2,4 @@
|
|||||||
///
|
///
|
||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
|
|
||||||
enum PostKind {
|
enum PostKind { TEXT, IMAGE, WEB_LINK, PDF, COUNTDOWN, SURVEY, YOUTUBE }
|
||||||
TEXT,
|
|
||||||
IMAGE,
|
|
||||||
WEB_LINK,
|
|
||||||
PDF,
|
|
||||||
MOVIE,
|
|
||||||
COUNTDOWN,
|
|
||||||
SURVEY,
|
|
||||||
YOUTUBE
|
|
||||||
}
|
|
||||||
|
@ -3,9 +3,8 @@ import 'package:comunic/helpers/preferences_helper.dart';
|
|||||||
import 'package:comunic/helpers/websocket_helper.dart';
|
import 'package:comunic/helpers/websocket_helper.dart';
|
||||||
import 'package:comunic/models/api_request.dart';
|
import 'package:comunic/models/api_request.dart';
|
||||||
import 'package:comunic/models/authentication_details.dart';
|
import 'package:comunic/models/authentication_details.dart';
|
||||||
import 'package:comunic/models/login_tokens.dart';
|
|
||||||
import 'package:comunic/models/new_account.dart';
|
import 'package:comunic/models/new_account.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:comunic/models/res_check_password_reset_token.dart';
|
||||||
|
|
||||||
/// Account helper
|
/// Account helper
|
||||||
///
|
///
|
||||||
@ -26,8 +25,6 @@ enum CreateAccountResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AccountHelper {
|
class AccountHelper {
|
||||||
static const _USER_ID_PREFERENCE_NAME = "user_id";
|
|
||||||
|
|
||||||
// Current user ID
|
// Current user ID
|
||||||
static int _currentUserID = -1;
|
static int _currentUserID = -1;
|
||||||
|
|
||||||
@ -36,7 +33,7 @@ class AccountHelper {
|
|||||||
/// Warning : This method MUST BE CALLED AT LEAST ONCE AFTER APP START !!!
|
/// Warning : This method MUST BE CALLED AT LEAST ONCE AFTER APP START !!!
|
||||||
Future<bool> signedIn() async {
|
Future<bool> signedIn() async {
|
||||||
bool signedIn =
|
bool signedIn =
|
||||||
(await PreferencesHelper.getInstance()).getLoginTokens() != null;
|
(await PreferencesHelper.getInstance()).getLoginToken() != null;
|
||||||
|
|
||||||
// Load current user ID for later use
|
// Load current user ID for later use
|
||||||
if (signedIn && _currentUserID == -1) await _loadCurrentUserID();
|
if (signedIn && _currentUserID == -1) await _loadCurrentUserID();
|
||||||
@ -47,8 +44,8 @@ class AccountHelper {
|
|||||||
/// Sign in user
|
/// Sign in user
|
||||||
Future<AuthResult> signIn(AuthenticationDetails auth) async {
|
Future<AuthResult> signIn(AuthenticationDetails auth) async {
|
||||||
final request = APIRequest(uri: "account/login");
|
final request = APIRequest(uri: "account/login");
|
||||||
request.addString("userMail", auth.email);
|
request.addString("mail", auth.email);
|
||||||
request.addString("userPassword", auth.password);
|
request.addString("password", auth.password);
|
||||||
|
|
||||||
final response = await APIHelper().exec(request);
|
final response = await APIHelper().exec(request);
|
||||||
|
|
||||||
@ -59,10 +56,9 @@ class AccountHelper {
|
|||||||
return AuthResult.TOO_MANY_ATTEMPTS;
|
return AuthResult.TOO_MANY_ATTEMPTS;
|
||||||
else if (response.code != 200) return AuthResult.NETWORK_ERROR;
|
else if (response.code != 200) return AuthResult.NETWORK_ERROR;
|
||||||
|
|
||||||
// Save login tokens
|
// Save login token
|
||||||
final tokensObj = response.getObject()["tokens"];
|
|
||||||
await (await PreferencesHelper.getInstance())
|
await (await PreferencesHelper.getInstance())
|
||||||
.setLoginTokens(LoginTokens(tokensObj["token1"], tokensObj["token2"]));
|
.setLoginToken(response.getObject()["token"]);
|
||||||
|
|
||||||
// Get current user ID
|
// Get current user ID
|
||||||
final userID = await _downloadCurrentUserID();
|
final userID = await _downloadCurrentUserID();
|
||||||
@ -72,8 +68,8 @@ class AccountHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save current user ID
|
// Save current user ID
|
||||||
final preferences = await SharedPreferences.getInstance();
|
final preferences = await PreferencesHelper.getInstance();
|
||||||
await preferences.setInt(_USER_ID_PREFERENCE_NAME, userID);
|
await preferences.setInt(PreferencesKeyList.USER_ID, userID);
|
||||||
_currentUserID = userID;
|
_currentUserID = userID;
|
||||||
|
|
||||||
return AuthResult.SUCCESS;
|
return AuthResult.SUCCESS;
|
||||||
@ -81,7 +77,11 @@ class AccountHelper {
|
|||||||
|
|
||||||
/// Sign out user
|
/// Sign out user
|
||||||
Future<void> signOut() async {
|
Future<void> signOut() async {
|
||||||
await (await PreferencesHelper.getInstance()).setLoginTokens(null);
|
await APIRequest.withLogin("account/logout").exec();
|
||||||
|
|
||||||
|
final preferencesHelper = await PreferencesHelper.getInstance();
|
||||||
|
await preferencesHelper.setLoginToken(null);
|
||||||
|
await preferencesHelper.setInt(PreferencesKeyList.USER_ID, -1);
|
||||||
_currentUserID = 0;
|
_currentUserID = 0;
|
||||||
|
|
||||||
// Close current web socket
|
// Close current web socket
|
||||||
@ -125,6 +125,11 @@ class AccountHelper {
|
|||||||
.execWithThrow())
|
.execWithThrow())
|
||||||
.getObject()["exists"];
|
.getObject()["exists"];
|
||||||
|
|
||||||
|
/// Get current user email address
|
||||||
|
static Future<String> getCurrentAccountEmailAddress() async =>
|
||||||
|
(await APIRequest.withLogin("account/mail")
|
||||||
|
.execWithThrowGetObject())["mail"];
|
||||||
|
|
||||||
/// Check out whether security questions have been set for an account or not
|
/// Check out whether security questions have been set for an account or not
|
||||||
///
|
///
|
||||||
/// Throws in case of failure
|
/// Throws in case of failure
|
||||||
@ -161,10 +166,19 @@ class AccountHelper {
|
|||||||
/// Check a password reset token
|
/// Check a password reset token
|
||||||
///
|
///
|
||||||
/// Throws in case failure
|
/// Throws in case failure
|
||||||
static Future<void> validatePasswordResetToken(String token) async =>
|
static Future<ResCheckPasswordToken> validatePasswordResetToken(
|
||||||
|
String token) async {
|
||||||
|
final response =
|
||||||
await APIRequest.withoutLogin("account/check_password_reset_token")
|
await APIRequest.withoutLogin("account/check_password_reset_token")
|
||||||
.addString("token", token)
|
.addString("token", token)
|
||||||
.execWithThrow();
|
.execWithThrowGetObject();
|
||||||
|
|
||||||
|
return ResCheckPasswordToken(
|
||||||
|
firstName: response["first_name"],
|
||||||
|
lastName: response["last_name"],
|
||||||
|
email: response["mail"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Change account password using password reset token
|
/// Change account password using password reset token
|
||||||
///
|
///
|
||||||
@ -178,10 +192,7 @@ class AccountHelper {
|
|||||||
|
|
||||||
/// Get current user ID from the server
|
/// Get current user ID from the server
|
||||||
Future<int> _downloadCurrentUserID() async {
|
Future<int> _downloadCurrentUserID() async {
|
||||||
final response = await APIRequest(
|
final response = await APIRequest.withLogin("account/id").exec();
|
||||||
uri: "user/getCurrentUserID",
|
|
||||||
needLogin: true,
|
|
||||||
).exec();
|
|
||||||
|
|
||||||
if (response.code != 200) return null;
|
if (response.code != 200) return null;
|
||||||
|
|
||||||
@ -190,8 +201,8 @@ class AccountHelper {
|
|||||||
|
|
||||||
/// Get the ID of the currently signed in user
|
/// Get the ID of the currently signed in user
|
||||||
Future<void> _loadCurrentUserID() async {
|
Future<void> _loadCurrentUserID() async {
|
||||||
final preferences = await SharedPreferences.getInstance();
|
final preferences = await PreferencesHelper.getInstance();
|
||||||
_currentUserID = preferences.getInt(_USER_ID_PREFERENCE_NAME);
|
_currentUserID = preferences.getInt(PreferencesKeyList.USER_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if current user ID is loaded or not
|
/// Check if current user ID is loaded or not
|
||||||
|
@ -19,15 +19,13 @@ class APIHelper {
|
|||||||
Future<APIResponse> exec(APIRequest request, {bool multipart = false}) async {
|
Future<APIResponse> exec(APIRequest request, {bool multipart = false}) async {
|
||||||
try {
|
try {
|
||||||
//Add API tokens
|
//Add API tokens
|
||||||
request.addString("serviceName", config().serviceName);
|
request.addString("client", config().clientName);
|
||||||
request.addString("serviceToken", config().serviceToken);
|
|
||||||
|
|
||||||
//Add user tokens (if required)
|
//Add user tokens (if required)
|
||||||
if (request.needLogin) {
|
if (request.needLogin) {
|
||||||
final tokens = (await PreferencesHelper.getInstance()).getLoginTokens();
|
final token = (await PreferencesHelper.getInstance()).getLoginToken();
|
||||||
assert(tokens != null);
|
assert(token != null);
|
||||||
request.addString("userToken1", tokens.tokenOne);
|
request.addString("token", token);
|
||||||
request.addString("userToken2", tokens.tokenTwo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine server URL
|
// Determine server URL
|
||||||
|
@ -16,7 +16,6 @@ const _NotificationElementTypeAPImapping = {
|
|||||||
"post_text": NotificationElementType.POST_TEXT,
|
"post_text": NotificationElementType.POST_TEXT,
|
||||||
"post_img": NotificationElementType.POST_IMAGE,
|
"post_img": NotificationElementType.POST_IMAGE,
|
||||||
"post_youtube": NotificationElementType.POST_YOUTUBE,
|
"post_youtube": NotificationElementType.POST_YOUTUBE,
|
||||||
"post_movie": NotificationElementType.POST_MOVIE,
|
|
||||||
"post_weblink": NotificationElementType.POST_WEBLINK,
|
"post_weblink": NotificationElementType.POST_WEBLINK,
|
||||||
"post_pdf": NotificationElementType.POST_PDF,
|
"post_pdf": NotificationElementType.POST_PDF,
|
||||||
"post_timer": NotificationElementType.POST_TIMER,
|
"post_timer": NotificationElementType.POST_TIMER,
|
||||||
|
@ -29,7 +29,6 @@ const _APIPostsKindsMap = {
|
|||||||
"image": PostKind.IMAGE,
|
"image": PostKind.IMAGE,
|
||||||
"weblink": PostKind.WEB_LINK,
|
"weblink": PostKind.WEB_LINK,
|
||||||
"pdf": PostKind.PDF,
|
"pdf": PostKind.PDF,
|
||||||
"movie": PostKind.MOVIE,
|
|
||||||
"countdown": PostKind.COUNTDOWN,
|
"countdown": PostKind.COUNTDOWN,
|
||||||
"survey": PostKind.SURVEY,
|
"survey": PostKind.SURVEY,
|
||||||
"youtube": PostKind.YOUTUBE
|
"youtube": PostKind.YOUTUBE
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:comunic/models/application_preferences.dart';
|
import 'package:comunic/models/application_preferences.dart';
|
||||||
import 'package:comunic/models/login_tokens.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
/// Preferences helper
|
/// Preferences helper
|
||||||
@ -11,14 +8,16 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
|
|
||||||
enum PreferencesKeyList {
|
enum PreferencesKeyList {
|
||||||
LOGIN_TOKENS,
|
USER_ID,
|
||||||
|
LOGIN_TOKEN,
|
||||||
ENABLE_DARK_THEME,
|
ENABLE_DARK_THEME,
|
||||||
FORCE_MOBILE_MODE,
|
FORCE_MOBILE_MODE,
|
||||||
SHOW_PERFORMANCE_OVERLAY,
|
SHOW_PERFORMANCE_OVERLAY,
|
||||||
}
|
}
|
||||||
|
|
||||||
const _PreferenceKeysName = {
|
const _PreferenceKeysName = {
|
||||||
PreferencesKeyList.LOGIN_TOKENS: "login_tokens",
|
PreferencesKeyList.USER_ID: "user_id",
|
||||||
|
PreferencesKeyList.LOGIN_TOKEN: "login_token",
|
||||||
PreferencesKeyList.ENABLE_DARK_THEME: "dark_theme",
|
PreferencesKeyList.ENABLE_DARK_THEME: "dark_theme",
|
||||||
PreferencesKeyList.FORCE_MOBILE_MODE: "force_mobile_mode",
|
PreferencesKeyList.FORCE_MOBILE_MODE: "force_mobile_mode",
|
||||||
PreferencesKeyList.SHOW_PERFORMANCE_OVERLAY: "perfs_overlay",
|
PreferencesKeyList.SHOW_PERFORMANCE_OVERLAY: "perfs_overlay",
|
||||||
@ -45,23 +44,28 @@ class PreferencesHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set new login tokens
|
/// Set new login tokens
|
||||||
Future<void> setLoginTokens(LoginTokens tokens) async {
|
Future<void> setLoginToken(String token) async {
|
||||||
await setString(PreferencesKeyList.LOGIN_TOKENS,
|
if (token != null)
|
||||||
tokens == null ? "null" : tokens.toString());
|
await setString(PreferencesKeyList.LOGIN_TOKEN, token);
|
||||||
|
else
|
||||||
|
await removeKey(PreferencesKeyList.LOGIN_TOKEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current [LoginTokens]. Returns null if none or in case of failure
|
/// Get current [LoginTokens]. Returns null if none or in case of failure
|
||||||
LoginTokens getLoginTokens() {
|
String getLoginToken() {
|
||||||
try {
|
try {
|
||||||
final string = getString(PreferencesKeyList.LOGIN_TOKENS);
|
final string = getString(PreferencesKeyList.LOGIN_TOKEN);
|
||||||
if (string == null || string == "null") return null;
|
return string;
|
||||||
return LoginTokens.fromJSON(jsonDecode(string));
|
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> removeKey(PreferencesKeyList key) async {
|
||||||
|
return await _sharedPreferences.remove(_PreferenceKeysName[key]);
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> setString(PreferencesKeyList key, String value) async {
|
Future<bool> setString(PreferencesKeyList key, String value) async {
|
||||||
return await _sharedPreferences.setString(_PreferenceKeysName[key], value);
|
return await _sharedPreferences.setString(_PreferenceKeysName[key], value);
|
||||||
}
|
}
|
||||||
@ -74,6 +78,14 @@ class PreferencesHelper {
|
|||||||
return await _sharedPreferences.setBool(_PreferenceKeysName[key], value);
|
return await _sharedPreferences.setBool(_PreferenceKeysName[key], value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> setInt(PreferencesKeyList key, int value) async {
|
||||||
|
return await _sharedPreferences.setInt(_PreferenceKeysName[key], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getInt(PreferencesKeyList key) {
|
||||||
|
return _sharedPreferences.getInt(_PreferenceKeysName[key]);
|
||||||
|
}
|
||||||
|
|
||||||
bool getBool(PreferencesKeyList key, {bool alternative = false}) {
|
bool getBool(PreferencesKeyList key, {bool alternative = false}) {
|
||||||
final v = _sharedPreferences.getBool(_PreferenceKeysName[key]);
|
final v = _sharedPreferences.getBool(_PreferenceKeysName[key]);
|
||||||
return v == null ? alternative : v;
|
return v == null ? alternative : v;
|
||||||
|
64
lib/helpers/server_config_helper.dart
Normal file
64
lib/helpers/server_config_helper.dart
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import 'package:comunic/models/api_request.dart';
|
||||||
|
import 'package:comunic/models/server_config.dart';
|
||||||
|
import 'package:version/version.dart';
|
||||||
|
|
||||||
|
/// Server configuration helper
|
||||||
|
///
|
||||||
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
|
class ServerConfigurationHelper {
|
||||||
|
static ServerConfig _config;
|
||||||
|
|
||||||
|
/// Make sure the configuration has been correctly loaded
|
||||||
|
static Future<void> ensureLoaded() async {
|
||||||
|
if (_config != null) return;
|
||||||
|
|
||||||
|
final response =
|
||||||
|
(await APIRequest.withoutLogin("server/config").execWithThrow())
|
||||||
|
.getObject();
|
||||||
|
|
||||||
|
final passwordPolicy = response["password_policy"];
|
||||||
|
final dataConservationPolicy = response["data_conservation_policy"];
|
||||||
|
|
||||||
|
_config = ServerConfig(
|
||||||
|
minSupportedMobileVersion:
|
||||||
|
Version.parse(response["min_supported_mobile_version"]),
|
||||||
|
termsURL: response["terms_url"],
|
||||||
|
playStoreURL: response["play_store_url"],
|
||||||
|
androidDirectDownloadURL: response["android_direct_download_url"],
|
||||||
|
passwordPolicy: PasswordPolicy(
|
||||||
|
allowMailInPassword: passwordPolicy["allow_email_in_password"],
|
||||||
|
allowNameInPassword: passwordPolicy["allow_name_in_password"],
|
||||||
|
minPasswordLength: passwordPolicy["min_password_length"],
|
||||||
|
minNumberUpperCaseLetters:
|
||||||
|
passwordPolicy["min_number_upper_case_letters"],
|
||||||
|
minNumberLowerCaseLetters:
|
||||||
|
passwordPolicy["min_number_lower_case_letters"],
|
||||||
|
minNumberDigits: passwordPolicy["min_number_digits"],
|
||||||
|
minNumberSpecialCharacters:
|
||||||
|
passwordPolicy["min_number_special_characters"],
|
||||||
|
minCategoriesPresence: passwordPolicy["min_categories_presence"],
|
||||||
|
),
|
||||||
|
dataConservationPolicy: ServerDataConservationPolicy(
|
||||||
|
minInactiveAccountLifetime:
|
||||||
|
dataConservationPolicy["min_inactive_account_lifetime"],
|
||||||
|
minNotificationLifetime:
|
||||||
|
dataConservationPolicy["min_notification_lifetime"],
|
||||||
|
minCommentsLifetime: dataConservationPolicy["min_comments_lifetime"],
|
||||||
|
minPostsLifetime: dataConservationPolicy["min_posts_lifetime"],
|
||||||
|
minConversationMessagesLifetime:
|
||||||
|
dataConservationPolicy["min_conversation_messages_lifetime"],
|
||||||
|
minLikesLifetime: dataConservationPolicy["min_likes_lifetime"],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current server configuration, throwing if it is not loaded yet
|
||||||
|
static ServerConfig get config {
|
||||||
|
if (_config == null)
|
||||||
|
throw Exception(
|
||||||
|
"Trying to access server configuration but it is not loaded yet!");
|
||||||
|
|
||||||
|
return _config;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:comunic/enums/user_page_visibility.dart';
|
import 'package:comunic/enums/user_page_visibility.dart';
|
||||||
import 'package:comunic/models/account_image_settings.dart';
|
import 'package:comunic/models/account_image_settings.dart';
|
||||||
import 'package:comunic/models/api_request.dart';
|
import 'package:comunic/models/api_request.dart';
|
||||||
|
import 'package:comunic/models/data_conservation_policy_settings.dart';
|
||||||
import 'package:comunic/models/general_settings.dart';
|
import 'package:comunic/models/general_settings.dart';
|
||||||
import 'package:comunic/models/new_emoji.dart';
|
import 'package:comunic/models/new_emoji.dart';
|
||||||
import 'package:comunic/models/security_settings.dart';
|
import 'package:comunic/models/security_settings.dart';
|
||||||
@ -193,4 +194,42 @@ class SettingsHelper {
|
|||||||
.addString("security_answer_2", newSettings.securityAnswer2)
|
.addString("security_answer_2", newSettings.securityAnswer2)
|
||||||
.execWithThrow();
|
.execWithThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get account data conservation policy settings
|
||||||
|
///
|
||||||
|
/// Throws in case of failure
|
||||||
|
static Future<DataConservationPolicySettings>
|
||||||
|
getDataConservationPolicy() async {
|
||||||
|
final response =
|
||||||
|
(await APIRequest.withLogin("settings/get_data_conservation_policy")
|
||||||
|
.execWithThrow())
|
||||||
|
.getObject();
|
||||||
|
|
||||||
|
return DataConservationPolicySettings(
|
||||||
|
inactiveAccountLifeTime: response["inactive_account_lifetime"],
|
||||||
|
notificationLifetime: response["notification_lifetime"],
|
||||||
|
commentsLifetime: response["comments_lifetime"],
|
||||||
|
postsLifetime: response["posts_lifetime"],
|
||||||
|
conversationMessagesLifetime:
|
||||||
|
response["conversation_messages_lifetime"],
|
||||||
|
likesLifetime: response["likes_lifetime"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply new data conservation policy settings
|
||||||
|
///
|
||||||
|
/// Throws in case of failure
|
||||||
|
static Future<void> setDataConservationPolicy(
|
||||||
|
String password, DataConservationPolicySettings newSettings) async {
|
||||||
|
await APIRequest(uri: "settings/set_data_conservation_policy", needLogin: true)
|
||||||
|
.addString("password", password)
|
||||||
|
.addInt("inactive_account_lifetime",
|
||||||
|
newSettings.inactiveAccountLifeTime ?? 0)
|
||||||
|
.addInt("notification_lifetime", newSettings.notificationLifetime ?? 0)
|
||||||
|
.addInt("comments_lifetime", newSettings.commentsLifetime ?? 0)
|
||||||
|
.addInt("posts_lifetime", newSettings.postsLifetime ?? 0)
|
||||||
|
.addInt("conversation_messages_lifetime",
|
||||||
|
newSettings.conversationMessagesLifetime ?? 0)
|
||||||
|
.addInt("likes_lifetime", newSettings.likesLifetime ?? 0)
|
||||||
|
.execWithThrow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
20
lib/helpers/version_helper.dart
Normal file
20
lib/helpers/version_helper.dart
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import 'package:package_info/package_info.dart';
|
||||||
|
import 'package:version/version.dart';
|
||||||
|
|
||||||
|
/// Application version helper
|
||||||
|
///
|
||||||
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
|
class VersionHelper {
|
||||||
|
static PackageInfo _info;
|
||||||
|
|
||||||
|
static Future<void> ensureLoaded() async {
|
||||||
|
_info = await PackageInfo.fromPlatform();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current version information
|
||||||
|
static PackageInfo get info => _info;
|
||||||
|
|
||||||
|
/// Get current application version, in parsed format
|
||||||
|
static Version get version => Version.parse(info.version);
|
||||||
|
}
|
@ -67,6 +67,7 @@ class WebSocketHelper {
|
|||||||
// Clear Futures queue
|
// Clear Futures queue
|
||||||
_requests.clear();
|
_requests.clear();
|
||||||
|
|
||||||
|
_ws = null;
|
||||||
EventsHelper.emit(WSClosedEvent());
|
EventsHelper.emit(WSClosedEvent());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:comunic/helpers/account_helper.dart';
|
import 'package:comunic/helpers/account_helper.dart';
|
||||||
import 'package:comunic/helpers/database/database_helper.dart';
|
import 'package:comunic/helpers/database/database_helper.dart';
|
||||||
import 'package:comunic/helpers/preferences_helper.dart';
|
import 'package:comunic/helpers/preferences_helper.dart';
|
||||||
import 'package:comunic/ui/routes/login_route.dart';
|
import 'package:comunic/helpers/version_helper.dart';
|
||||||
import 'package:comunic/ui/widgets/init_widget.dart';
|
import 'package:comunic/ui/widgets/init_widget.dart';
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -13,6 +13,9 @@ import 'package:flutter/material.dart';
|
|||||||
void subMain() async {
|
void subMain() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Load package information
|
||||||
|
await VersionHelper.ensureLoaded();
|
||||||
|
|
||||||
// Connect to database
|
// Connect to database
|
||||||
await DatabaseHelper.open();
|
await DatabaseHelper.open();
|
||||||
await DatabaseHelper.cleanUpDatabase();
|
await DatabaseHelper.cleanUpDatabase();
|
||||||
@ -51,7 +54,7 @@ class ComunicApplicationState extends State<ComunicApplication> {
|
|||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
home: AccountHelper.isUserIDLoaded ? InitializeWidget() : LoginRoute(),
|
home: InitializeWidget(),
|
||||||
theme: prefs.enableDarkMode ? ThemeData.dark() : null,
|
theme: prefs.enableDarkMode ? ThemeData.dark() : null,
|
||||||
showPerformanceOverlay: prefs.showPerformancesOverlay,
|
showPerformanceOverlay: prefs.showPerformancesOverlay,
|
||||||
);
|
);
|
||||||
|
@ -23,9 +23,7 @@ void main() {
|
|||||||
apiServerName: "192.168.1.9:3000",
|
apiServerName: "192.168.1.9:3000",
|
||||||
apiServerUri: "/",
|
apiServerUri: "/",
|
||||||
apiServerSecure: false,
|
apiServerSecure: false,
|
||||||
serviceName: "ComunicFlutter",
|
clientName: "ComunicFlutter",
|
||||||
serviceToken: "G9sZCBmb3IgVWJ1bnR1CkNvbW1lbnRbbmVdPeCkieCkrOCkq",
|
|
||||||
termsOfServicesURL: "http://devweb.local/comunic/current/about.php?cgu",
|
|
||||||
));
|
));
|
||||||
|
|
||||||
HttpOverrides.global = new MyHttpOverride();
|
HttpOverrides.global = new MyHttpOverride();
|
||||||
|
@ -10,9 +10,7 @@ void main() {
|
|||||||
apiServerName: "api.communiquons.org",
|
apiServerName: "api.communiquons.org",
|
||||||
apiServerUri: "/",
|
apiServerUri: "/",
|
||||||
apiServerSecure: true,
|
apiServerSecure: true,
|
||||||
serviceName: "ComunicFlutter",
|
clientName: "ComunicFlutter",
|
||||||
serviceToken: "9KfSwmB76U9UUwjXngDG7PeYccNfy",
|
|
||||||
termsOfServicesURL: "https://about.communiquons.org/about/terms/",
|
|
||||||
));
|
));
|
||||||
|
|
||||||
subMain();
|
subMain();
|
||||||
|
@ -88,6 +88,10 @@ class APIRequest {
|
|||||||
/// Execute the request, throws an exception in case of failure
|
/// Execute the request, throws an exception in case of failure
|
||||||
Future<APIResponse> execWithThrow() async => (await exec()).assertOk();
|
Future<APIResponse> execWithThrow() async => (await exec()).assertOk();
|
||||||
|
|
||||||
|
/// Execute the request, throws an exception in case of failure
|
||||||
|
Future<Map<String, dynamic>> execWithThrowGetObject() async =>
|
||||||
|
(await execWithThrow()).getObject();
|
||||||
|
|
||||||
/// Execute the request with files
|
/// Execute the request with files
|
||||||
Future<APIResponse> execWithFiles() async => APIHelper().execWithFiles(this);
|
Future<APIResponse> execWithFiles() async => APIHelper().execWithFiles(this);
|
||||||
|
|
||||||
|
@ -9,23 +9,17 @@ class Config {
|
|||||||
final String apiServerName;
|
final String apiServerName;
|
||||||
final String apiServerUri;
|
final String apiServerUri;
|
||||||
final bool apiServerSecure;
|
final bool apiServerSecure;
|
||||||
final String serviceName;
|
final String clientName;
|
||||||
final String serviceToken;
|
|
||||||
final String termsOfServicesURL;
|
|
||||||
|
|
||||||
const Config({
|
const Config({
|
||||||
@required this.apiServerName,
|
@required this.apiServerName,
|
||||||
@required this.apiServerUri,
|
@required this.apiServerUri,
|
||||||
@required this.apiServerSecure,
|
@required this.apiServerSecure,
|
||||||
@required this.serviceName,
|
@required this.clientName,
|
||||||
@required this.serviceToken,
|
|
||||||
@required this.termsOfServicesURL,
|
|
||||||
}) : assert(apiServerName != null),
|
}) : assert(apiServerName != null),
|
||||||
assert(apiServerUri != null),
|
assert(apiServerUri != null),
|
||||||
assert(apiServerSecure != null),
|
assert(apiServerSecure != null),
|
||||||
assert(serviceName != null),
|
assert(clientName != null);
|
||||||
assert(serviceToken != null),
|
|
||||||
assert(termsOfServicesURL != null);
|
|
||||||
|
|
||||||
/// Get and set static configuration
|
/// Get and set static configuration
|
||||||
static Config _config;
|
static Config _config;
|
||||||
|
21
lib/models/data_conservation_policy_settings.dart
Normal file
21
lib/models/data_conservation_policy_settings.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/// Data conservation policy settings
|
||||||
|
///
|
||||||
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
|
class DataConservationPolicySettings {
|
||||||
|
int inactiveAccountLifeTime;
|
||||||
|
int notificationLifetime;
|
||||||
|
int commentsLifetime;
|
||||||
|
int postsLifetime;
|
||||||
|
int conversationMessagesLifetime;
|
||||||
|
int likesLifetime;
|
||||||
|
|
||||||
|
DataConservationPolicySettings({
|
||||||
|
this.inactiveAccountLifeTime,
|
||||||
|
this.notificationLifetime,
|
||||||
|
this.commentsLifetime,
|
||||||
|
this.postsLifetime,
|
||||||
|
this.conversationMessagesLifetime,
|
||||||
|
this.likesLifetime,
|
||||||
|
});
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
/// Login tokens model
|
|
||||||
///
|
|
||||||
/// @author Pierre HUBERT
|
|
||||||
|
|
||||||
class LoginTokens {
|
|
||||||
final String tokenOne;
|
|
||||||
final String tokenTwo;
|
|
||||||
|
|
||||||
const LoginTokens(this.tokenOne, this.tokenTwo)
|
|
||||||
: assert(tokenOne != null),
|
|
||||||
assert(tokenTwo != null);
|
|
||||||
|
|
||||||
LoginTokens.fromJSON(Map<String, dynamic> json)
|
|
||||||
: tokenOne = json["token_one"],
|
|
||||||
tokenTwo = json["token_two"];
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return jsonEncode({"token_one": tokenOne, "token_two": tokenTwo});
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,7 +13,6 @@ enum NotificationElementType {
|
|||||||
POST_TEXT,
|
POST_TEXT,
|
||||||
POST_IMAGE,
|
POST_IMAGE,
|
||||||
POST_YOUTUBE,
|
POST_YOUTUBE,
|
||||||
POST_MOVIE,
|
|
||||||
POST_WEBLINK,
|
POST_WEBLINK,
|
||||||
POST_PDF,
|
POST_PDF,
|
||||||
POST_TIMER,
|
POST_TIMER,
|
||||||
|
19
lib/models/res_check_password_reset_token.dart
Normal file
19
lib/models/res_check_password_reset_token.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Check password reset token result
|
||||||
|
///
|
||||||
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
|
class ResCheckPasswordToken {
|
||||||
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
|
final String email;
|
||||||
|
|
||||||
|
const ResCheckPasswordToken({
|
||||||
|
@required this.firstName,
|
||||||
|
@required this.lastName,
|
||||||
|
@required this.email,
|
||||||
|
}) : assert(firstName != null),
|
||||||
|
assert(lastName != null),
|
||||||
|
assert(email != null);
|
||||||
|
}
|
81
lib/models/server_config.dart
Normal file
81
lib/models/server_config.dart
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:version/version.dart';
|
||||||
|
|
||||||
|
/// Server static configuration
|
||||||
|
///
|
||||||
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
|
class PasswordPolicy {
|
||||||
|
final bool allowMailInPassword;
|
||||||
|
final bool allowNameInPassword;
|
||||||
|
final int minPasswordLength;
|
||||||
|
final int minNumberUpperCaseLetters;
|
||||||
|
final int minNumberLowerCaseLetters;
|
||||||
|
final int minNumberDigits;
|
||||||
|
final int minNumberSpecialCharacters;
|
||||||
|
final int minCategoriesPresence;
|
||||||
|
|
||||||
|
const PasswordPolicy({
|
||||||
|
@required this.allowMailInPassword,
|
||||||
|
@required this.allowNameInPassword,
|
||||||
|
@required this.minPasswordLength,
|
||||||
|
@required this.minNumberUpperCaseLetters,
|
||||||
|
@required this.minNumberLowerCaseLetters,
|
||||||
|
@required this.minNumberDigits,
|
||||||
|
@required this.minNumberSpecialCharacters,
|
||||||
|
@required this.minCategoriesPresence,
|
||||||
|
}) : assert(allowMailInPassword != null),
|
||||||
|
assert(allowNameInPassword != null),
|
||||||
|
assert(minPasswordLength != null),
|
||||||
|
assert(minNumberUpperCaseLetters != null),
|
||||||
|
assert(minNumberLowerCaseLetters != null),
|
||||||
|
assert(minNumberDigits != null),
|
||||||
|
assert(minNumberSpecialCharacters != null),
|
||||||
|
assert(minCategoriesPresence != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerDataConservationPolicy {
|
||||||
|
final int minInactiveAccountLifetime;
|
||||||
|
final int minNotificationLifetime;
|
||||||
|
final int minCommentsLifetime;
|
||||||
|
final int minPostsLifetime;
|
||||||
|
final int minConversationMessagesLifetime;
|
||||||
|
final int minLikesLifetime;
|
||||||
|
|
||||||
|
const ServerDataConservationPolicy({
|
||||||
|
@required this.minInactiveAccountLifetime,
|
||||||
|
@required this.minNotificationLifetime,
|
||||||
|
@required this.minCommentsLifetime,
|
||||||
|
@required this.minPostsLifetime,
|
||||||
|
@required this.minConversationMessagesLifetime,
|
||||||
|
@required this.minLikesLifetime,
|
||||||
|
}) : assert(minInactiveAccountLifetime != null),
|
||||||
|
assert(minNotificationLifetime != null),
|
||||||
|
assert(minCommentsLifetime != null),
|
||||||
|
assert(minPostsLifetime != null),
|
||||||
|
assert(minConversationMessagesLifetime != null),
|
||||||
|
assert(minLikesLifetime != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerConfig {
|
||||||
|
final Version minSupportedMobileVersion;
|
||||||
|
final String termsURL;
|
||||||
|
final String playStoreURL;
|
||||||
|
final String androidDirectDownloadURL;
|
||||||
|
final PasswordPolicy passwordPolicy;
|
||||||
|
final ServerDataConservationPolicy dataConservationPolicy;
|
||||||
|
|
||||||
|
const ServerConfig({
|
||||||
|
@required this.minSupportedMobileVersion,
|
||||||
|
@required this.termsURL,
|
||||||
|
@required this.playStoreURL,
|
||||||
|
@required this.androidDirectDownloadURL,
|
||||||
|
@required this.passwordPolicy,
|
||||||
|
@required this.dataConservationPolicy,
|
||||||
|
}) : assert(minSupportedMobileVersion != null),
|
||||||
|
assert(termsURL != null),
|
||||||
|
assert(playStoreURL != null),
|
||||||
|
assert(androidDirectDownloadURL != null),
|
||||||
|
assert(passwordPolicy != null),
|
||||||
|
assert(dataConservationPolicy != null);
|
||||||
|
}
|
40
lib/ui/dialogs/deprecation_dialog.dart
Normal file
40
lib/ui/dialogs/deprecation_dialog.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import 'package:comunic/helpers/server_config_helper.dart';
|
||||||
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
/// Deprecation dialog
|
||||||
|
///
|
||||||
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
|
/// Show a dialog to warn the user this version of the application is deprecated
|
||||||
|
Future<void> showDeprecationDialog(BuildContext context) async {
|
||||||
|
await showDialog(context: context, builder: (c) => _DeprecationDialog());
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeprecationDialog extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(tr("Deprecated application version")),
|
||||||
|
content: Text(tr(
|
||||||
|
"This version of the Comunic application is deprecated. You might still be able to use it, but some features may not work. We recommend you to update to the latest version of the application.")),
|
||||||
|
actions: [
|
||||||
|
MaterialButton(
|
||||||
|
onPressed: () =>
|
||||||
|
launch(ServerConfigurationHelper.config.playStoreURL),
|
||||||
|
child: Text(tr("Go to the Play Store")),
|
||||||
|
),
|
||||||
|
MaterialButton(
|
||||||
|
onPressed: () =>
|
||||||
|
launch(ServerConfigurationHelper.config.androidDirectDownloadURL),
|
||||||
|
child: Text(tr("Download update outside Play Store")),
|
||||||
|
),
|
||||||
|
MaterialButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(tr("Use the old application anyway")),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:comunic/ui/widgets/dialogs/auto_sized_dialog_content_widget.dart';
|
import 'package:comunic/ui/widgets/dialogs/auto_sized_dialog_content_widget.dart';
|
||||||
import 'package:comunic/utils/input_utils.dart';
|
import 'package:comunic/ui/widgets/new_password_input_widget.dart';
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -8,27 +8,44 @@ import 'package:flutter/material.dart';
|
|||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
|
|
||||||
/// Ask the user to enter a new password
|
/// Ask the user to enter a new password
|
||||||
Future<String> showInputNewPassword(BuildContext context) async {
|
Future<String> showInputNewPassword({
|
||||||
|
@required BuildContext context,
|
||||||
|
@required UserInfoForPassword userInfo,
|
||||||
|
}) async {
|
||||||
|
assert(context != null);
|
||||||
|
assert(userInfo != null);
|
||||||
return await showDialog(
|
return await showDialog(
|
||||||
context: context, builder: (c) => _InputNewPasswordDialog());
|
context: context,
|
||||||
|
builder: (c) => _InputNewPasswordDialog(
|
||||||
|
userInfo: userInfo,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InputNewPasswordDialog extends StatefulWidget {
|
class _InputNewPasswordDialog extends StatefulWidget {
|
||||||
|
final UserInfoForPassword userInfo;
|
||||||
|
|
||||||
|
const _InputNewPasswordDialog({
|
||||||
|
Key key,
|
||||||
|
@required this.userInfo,
|
||||||
|
}) : assert(userInfo != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
__InputNewPasswordDialogState createState() =>
|
__InputNewPasswordDialogState createState() =>
|
||||||
__InputNewPasswordDialogState();
|
__InputNewPasswordDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class __InputNewPasswordDialogState extends State<_InputNewPasswordDialog> {
|
class __InputNewPasswordDialogState extends State<_InputNewPasswordDialog> {
|
||||||
final _controller1 = TextEditingController();
|
final _controller1 = GlobalKey<NewPasswordInputWidgetState>();
|
||||||
final _controller2 = TextEditingController();
|
final _controller2 = TextEditingController();
|
||||||
final _focusScopeNode = FocusScopeNode();
|
final _focusScopeNode = FocusScopeNode();
|
||||||
|
|
||||||
String get _password => _controller1.text;
|
String get _password => _controller1.currentState.value;
|
||||||
|
|
||||||
bool get _input1Valid => validatePassword(_password);
|
bool get _input1Valid =>
|
||||||
|
_controller1.currentState != null && _controller1.currentState.valid;
|
||||||
|
|
||||||
bool get _input2Valid => _controller1.text == _controller2.text;
|
bool get _input2Valid => _controller1.currentState.value == _controller2.text;
|
||||||
|
|
||||||
bool get _isValid => _input1Valid && _input2Valid;
|
bool get _isValid => _input1Valid && _input2Valid;
|
||||||
|
|
||||||
@ -65,12 +82,10 @@ class __InputNewPasswordDialogState extends State<_InputNewPasswordDialog> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// Input 1
|
// Input 1
|
||||||
_buildPasswordField(
|
NewPasswordInputWidget(
|
||||||
controller: _controller1,
|
key: _controller1,
|
||||||
|
user: widget.userInfo,
|
||||||
label: tr("Your new password"),
|
label: tr("Your new password"),
|
||||||
errorText: _controller1.text.isNotEmpty && !_input1Valid
|
|
||||||
? tr("Invalid password!")
|
|
||||||
: null,
|
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
onSubmitted: () => _focusScopeNode.nextFocus(),
|
onSubmitted: () => _focusScopeNode.nextFocus(),
|
||||||
),
|
),
|
||||||
|
@ -30,7 +30,7 @@ class __InputUserPasswordDialogState
|
|||||||
String get _currPass => _controller.text;
|
String get _currPass => _controller.text;
|
||||||
|
|
||||||
bool get _canSubmit =>
|
bool get _canSubmit =>
|
||||||
validatePassword(_controller.text) && _status != _Status.CHECKING;
|
legacyValidatePassword(_controller.text) && _status != _Status.CHECKING;
|
||||||
|
|
||||||
void _setStatus(_Status s) => setState(() => _status = s);
|
void _setStatus(_Status s) => setState(() => _status = s);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:comunic/helpers/account_helper.dart';
|
import 'package:comunic/helpers/account_helper.dart';
|
||||||
import 'package:comunic/models/config.dart';
|
import 'package:comunic/helpers/server_config_helper.dart';
|
||||||
import 'package:comunic/models/new_account.dart';
|
import 'package:comunic/models/new_account.dart';
|
||||||
|
import 'package:comunic/ui/widgets/new_password_input_widget.dart';
|
||||||
import 'package:comunic/utils/input_utils.dart';
|
import 'package:comunic/utils/input_utils.dart';
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:comunic/utils/ui_utils.dart';
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
@ -34,7 +35,7 @@ class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> {
|
|||||||
final _firstNameController = TextEditingController();
|
final _firstNameController = TextEditingController();
|
||||||
final _lastNameController = TextEditingController();
|
final _lastNameController = TextEditingController();
|
||||||
final _emailController = TextEditingController();
|
final _emailController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordInputKey = GlobalKey<NewPasswordInputWidgetState>();
|
||||||
final _verifyPasswordController = TextEditingController();
|
final _verifyPasswordController = TextEditingController();
|
||||||
bool _acceptedTOS = false;
|
bool _acceptedTOS = false;
|
||||||
|
|
||||||
@ -49,10 +50,11 @@ class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> {
|
|||||||
|
|
||||||
bool get _isEmailValid => validateEmail(_emailController.text);
|
bool get _isEmailValid => validateEmail(_emailController.text);
|
||||||
|
|
||||||
bool get _isPasswordValid => _passwordController.text.length > 3;
|
bool get _isPasswordValid => _passwordInputKey.currentState.valid;
|
||||||
|
|
||||||
bool get _isPasswordConfirmationValid =>
|
bool get _isPasswordConfirmationValid =>
|
||||||
_passwordController.text == _verifyPasswordController.text;
|
_passwordInputKey.currentState != null &&
|
||||||
|
_passwordInputKey.currentState.value == _verifyPasswordController.text;
|
||||||
|
|
||||||
bool get _isFormValid =>
|
bool get _isFormValid =>
|
||||||
_isFirstNameValid &&
|
_isFirstNameValid &&
|
||||||
@ -69,7 +71,7 @@ class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> {
|
|||||||
? tr(
|
? tr(
|
||||||
"Too many accounts have been created from this IP address for now. Please try again later.")
|
"Too many accounts have been created from this IP address for now. Please try again later.")
|
||||||
: tr(
|
: tr(
|
||||||
"An error occured while creating your account. Please try again.");
|
"An error occurred while creating your account. Please try again.");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -124,15 +126,16 @@ class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Password
|
// Password
|
||||||
_InputEntry(
|
NewPasswordInputWidget(
|
||||||
controller: _passwordController,
|
key: _passwordInputKey,
|
||||||
label: tr("Password"),
|
label: tr("Password"),
|
||||||
onEdited: _updateUI,
|
onEdited: _updateUI,
|
||||||
icon: Icon(Icons.lock),
|
icon: Icon(Icons.lock),
|
||||||
isPassword: true,
|
user: UserInfoForPassword(
|
||||||
error: _showErrors && !_isPasswordValid
|
firstName: _firstNameController.text,
|
||||||
? tr("Invalid password!")
|
lastName: _lastNameController.text,
|
||||||
: null,
|
email: _emailController.text,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Verify password
|
// Verify password
|
||||||
@ -206,7 +209,7 @@ class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> {
|
|||||||
firstName: _firstNameController.text,
|
firstName: _firstNameController.text,
|
||||||
lastName: _lastNameController.text,
|
lastName: _lastNameController.text,
|
||||||
email: _emailController.text,
|
email: _emailController.text,
|
||||||
password: _passwordController.text,
|
password: _passwordInputKey.currentState.value,
|
||||||
));
|
));
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -223,9 +226,7 @@ class __CreateAccountRouteBodyState extends State<_CreateAccountRouteBody> {
|
|||||||
_accountCreated();
|
_accountCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openTOS() {
|
void _openTOS() => launch(ServerConfigurationHelper.config.termsURL);
|
||||||
launch(config().termsOfServicesURL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showCreateAccountError() async {
|
void _showCreateAccountError() async {
|
||||||
await showCupertinoDialog(
|
await showCupertinoDialog(
|
||||||
|
@ -13,7 +13,7 @@ import 'package:flutter/rendering.dart';
|
|||||||
///
|
///
|
||||||
/// @author Pierre Hubert
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
class ResetPasswordRoute extends StatelessWidget {
|
class ForgotPasswordRoute extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -133,8 +133,8 @@ class _ResetPasswordBodyState extends SafeState<_ResetPasswordBody> {
|
|||||||
_setLoading(false);
|
_setLoading(false);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
print("Could not check given email! $e\n$s");
|
print("Could not check given email! $e\n$s");
|
||||||
showSimpleSnack(
|
showSimpleSnack(context,
|
||||||
context, tr("An error occurred while checking your recovery options !"));
|
tr("An error occurred while checking your recovery options !"));
|
||||||
_setLoading(false);
|
_setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:comunic/helpers/account_helper.dart';
|
import 'package:comunic/helpers/account_helper.dart';
|
||||||
import 'package:comunic/models/authentication_details.dart';
|
import 'package:comunic/models/authentication_details.dart';
|
||||||
import 'package:comunic/ui/routes/create_account_route.dart';
|
import 'package:comunic/ui/routes/create_account_route.dart';
|
||||||
import 'package:comunic/ui/routes/reset_password_route.dart';
|
import 'package:comunic/ui/routes/forgot_password_route.dart';
|
||||||
import 'package:comunic/ui/widgets/init_widget.dart';
|
import 'package:comunic/ui/widgets/init_widget.dart';
|
||||||
import 'package:comunic/ui/widgets/login_scaffold.dart';
|
import 'package:comunic/ui/widgets/login_scaffold.dart';
|
||||||
import 'package:comunic/utils/input_utils.dart';
|
import 'package:comunic/utils/input_utils.dart';
|
||||||
@ -69,7 +69,7 @@ class _LoginRouteState extends State<LoginRoute> {
|
|||||||
|
|
||||||
void _openResetPasswordPage() {
|
void _openResetPasswordPage() {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.push(MaterialPageRoute(builder: (c) => ResetPasswordRoute()));
|
.push(MaterialPageRoute(builder: (c) => ForgotPasswordRoute()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build error card
|
/// Build error card
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:comunic/helpers/account_helper.dart';
|
import 'package:comunic/helpers/account_helper.dart';
|
||||||
import 'package:comunic/ui/routes/conversation_route.dart';
|
import 'package:comunic/ui/routes/conversation_route.dart';
|
||||||
import 'package:comunic/ui/routes/login_route.dart';
|
|
||||||
import 'package:comunic/ui/routes/main_route/page_info.dart';
|
import 'package:comunic/ui/routes/main_route/page_info.dart';
|
||||||
import 'package:comunic/ui/routes/settings/account_settings_route.dart';
|
import 'package:comunic/ui/routes/settings/account_settings_route.dart';
|
||||||
import 'package:comunic/ui/screens/call_screen.dart';
|
import 'package:comunic/ui/screens/call_screen.dart';
|
||||||
@ -170,10 +169,6 @@ abstract class MainController extends State<MainRoute> {
|
|||||||
popUntilMainRoute();
|
popUntilMainRoute();
|
||||||
|
|
||||||
await AccountHelper().signOut();
|
await AccountHelper().signOut();
|
||||||
|
|
||||||
Navigator.pushReplacement(context, MaterialPageRoute(builder: (c) {
|
|
||||||
return LoginRoute();
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pop current page. Last page can not be popped
|
/// Pop current page. Last page can not be popped
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:comunic/helpers/account_helper.dart';
|
import 'package:comunic/helpers/account_helper.dart';
|
||||||
|
import 'package:comunic/models/res_check_password_reset_token.dart';
|
||||||
import 'package:comunic/ui/dialogs/input_new_password_dialog.dart';
|
import 'package:comunic/ui/dialogs/input_new_password_dialog.dart';
|
||||||
import 'package:comunic/ui/widgets/async_screen_widget.dart';
|
import 'package:comunic/ui/widgets/async_screen_widget.dart';
|
||||||
|
import 'package:comunic/ui/widgets/new_password_input_widget.dart';
|
||||||
import 'package:comunic/ui/widgets/safe_state.dart';
|
import 'package:comunic/ui/widgets/safe_state.dart';
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:comunic/utils/ui_utils.dart';
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
@ -47,12 +49,13 @@ class __PasswordResetBodyState extends SafeState<_PasswordResetBody> {
|
|||||||
final _key = GlobalKey<AsyncScreenWidgetState>();
|
final _key = GlobalKey<AsyncScreenWidgetState>();
|
||||||
|
|
||||||
var _status = _Status.BEFORE_CHANGE;
|
var _status = _Status.BEFORE_CHANGE;
|
||||||
|
ResCheckPasswordToken _tokenInfo;
|
||||||
|
|
||||||
void _setStatus(_Status s) => setState(() => _status = s);
|
void _setStatus(_Status s) => setState(() => _status = s);
|
||||||
|
|
||||||
Future<void> _validateToken() async {
|
Future<void> _validateToken() async {
|
||||||
_status = _Status.BEFORE_CHANGE;
|
_status = _Status.BEFORE_CHANGE;
|
||||||
await AccountHelper.validatePasswordResetToken(widget.token);
|
_tokenInfo = await AccountHelper.validatePasswordResetToken(widget.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -108,7 +111,14 @@ class __PasswordResetBodyState extends SafeState<_PasswordResetBody> {
|
|||||||
void _changePassword() async {
|
void _changePassword() async {
|
||||||
try {
|
try {
|
||||||
// Ask for new password
|
// Ask for new password
|
||||||
final newPass = await showInputNewPassword(context);
|
final newPass = await showInputNewPassword(
|
||||||
|
context: context,
|
||||||
|
userInfo: UserInfoForPassword(
|
||||||
|
firstName: _tokenInfo.firstName,
|
||||||
|
lastName: _tokenInfo.lastName,
|
||||||
|
email: _tokenInfo.email,
|
||||||
|
),
|
||||||
|
);
|
||||||
if (newPass == null) return;
|
if (newPass == null) return;
|
||||||
_setStatus(_Status.WHILE_CHANGE);
|
_setStatus(_Status.WHILE_CHANGE);
|
||||||
|
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import 'package:comunic/helpers/account_helper.dart';
|
import 'package:comunic/helpers/account_helper.dart';
|
||||||
|
import 'package:comunic/helpers/server_config_helper.dart';
|
||||||
|
import 'package:comunic/helpers/settings_helper.dart';
|
||||||
|
import 'package:comunic/models/data_conservation_policy_settings.dart';
|
||||||
|
import 'package:comunic/models/server_config.dart';
|
||||||
import 'package:comunic/ui/dialogs/input_user_password_dialog.dart';
|
import 'package:comunic/ui/dialogs/input_user_password_dialog.dart';
|
||||||
|
import 'package:comunic/ui/dialogs/multi_choices_dialog.dart';
|
||||||
|
import 'package:comunic/ui/widgets/async_screen_widget.dart';
|
||||||
import 'package:comunic/ui/widgets/settings/header_spacer_section.dart';
|
import 'package:comunic/ui/widgets/settings/header_spacer_section.dart';
|
||||||
|
import 'package:comunic/ui/widgets/settings/multi_choices_settings_tile.dart';
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:comunic/utils/ui_utils.dart';
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
@ -17,19 +24,117 @@ class AccountPrivacySettings extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AccountPrivacySettingsState extends State<AccountPrivacySettings> {
|
class _AccountPrivacySettingsState extends State<AccountPrivacySettings> {
|
||||||
|
final _key = GlobalKey<AsyncScreenWidgetState>();
|
||||||
|
ServerConfig _serverConfig;
|
||||||
|
DataConservationPolicySettings _userSettings;
|
||||||
|
String _cachedPassword;
|
||||||
|
|
||||||
|
Future<void> _loadSettings() async {
|
||||||
|
_serverConfig = ServerConfigurationHelper.config;
|
||||||
|
_userSettings = await SettingsHelper.getDataConservationPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SettingsList(sections: [
|
return AsyncScreenWidget(
|
||||||
|
key: _key,
|
||||||
|
onReload: _loadSettings,
|
||||||
|
onBuild: () => SettingsList(sections: [
|
||||||
HeadSpacerSection(),
|
HeadSpacerSection(),
|
||||||
SettingsSection(title: tr("Privacy settings"), tiles: [
|
SettingsSection(
|
||||||
|
title: tr("Data conservation policy"),
|
||||||
|
tiles: _dataConservationPolicyTiles,
|
||||||
|
),
|
||||||
|
HeadSpacerSection(),
|
||||||
|
HeadSpacerSection(),
|
||||||
|
SettingsSection(title: tr("Danger zone"), tiles: [
|
||||||
SettingsTile(
|
SettingsTile(
|
||||||
title: tr("Delete your account"),
|
title: tr("Delete your account"),
|
||||||
subtitle:
|
subtitle: tr(
|
||||||
tr("Permanently delete your account and all data related to it."),
|
"Permanently delete your account and all data related to it."),
|
||||||
onPressed: (_) => _deleteAccount(),
|
onPressed: (_) => _deleteAccount(),
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
]);
|
]),
|
||||||
|
errorMessage: tr("Failed to load privacy settings!"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SettingsTile> get _dataConservationPolicyTiles => [
|
||||||
|
DataConservationPolicyTile(
|
||||||
|
value: _userSettings.notificationLifetime,
|
||||||
|
title: tr("Automatically delete unread notifications after"),
|
||||||
|
onChange: (val) {
|
||||||
|
_userSettings.notificationLifetime = val;
|
||||||
|
_updateDataConservationPolicy();
|
||||||
|
},
|
||||||
|
minValue:
|
||||||
|
_serverConfig.dataConservationPolicy.minNotificationLifetime,
|
||||||
|
),
|
||||||
|
DataConservationPolicyTile(
|
||||||
|
value: _userSettings.commentsLifetime,
|
||||||
|
title: tr("Automatically delete your comments after"),
|
||||||
|
onChange: (val) {
|
||||||
|
_userSettings.commentsLifetime = val;
|
||||||
|
_updateDataConservationPolicy();
|
||||||
|
},
|
||||||
|
minValue: _serverConfig.dataConservationPolicy.minCommentsLifetime,
|
||||||
|
),
|
||||||
|
DataConservationPolicyTile(
|
||||||
|
value: _userSettings.postsLifetime,
|
||||||
|
title: tr("Automatically delete your posts after"),
|
||||||
|
onChange: (val) {
|
||||||
|
_userSettings.postsLifetime = val;
|
||||||
|
_updateDataConservationPolicy();
|
||||||
|
},
|
||||||
|
minValue: _serverConfig.dataConservationPolicy.minPostsLifetime,
|
||||||
|
),
|
||||||
|
DataConservationPolicyTile(
|
||||||
|
value: _userSettings.conversationMessagesLifetime,
|
||||||
|
title: tr("Automatically delete your conversation messages after"),
|
||||||
|
onChange: (val) {
|
||||||
|
_userSettings.conversationMessagesLifetime = val;
|
||||||
|
_updateDataConservationPolicy();
|
||||||
|
},
|
||||||
|
minValue: _serverConfig
|
||||||
|
.dataConservationPolicy.minConversationMessagesLifetime,
|
||||||
|
),
|
||||||
|
DataConservationPolicyTile(
|
||||||
|
value: _userSettings.likesLifetime,
|
||||||
|
title: tr("Automatically delete your likes after"),
|
||||||
|
onChange: (val) {
|
||||||
|
_userSettings.likesLifetime = val;
|
||||||
|
_updateDataConservationPolicy();
|
||||||
|
},
|
||||||
|
minValue: _serverConfig.dataConservationPolicy.minLikesLifetime,
|
||||||
|
),
|
||||||
|
DataConservationPolicyTile(
|
||||||
|
value: _userSettings.inactiveAccountLifeTime,
|
||||||
|
title: tr(
|
||||||
|
"Automatically delete your account if you have been inactive for"),
|
||||||
|
onChange: (val) {
|
||||||
|
_userSettings.inactiveAccountLifeTime = val;
|
||||||
|
_updateDataConservationPolicy();
|
||||||
|
},
|
||||||
|
minValue:
|
||||||
|
_serverConfig.dataConservationPolicy.minInactiveAccountLifetime,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
void _updateDataConservationPolicy() async {
|
||||||
|
try {
|
||||||
|
if (_cachedPassword == null)
|
||||||
|
_cachedPassword = await showUserPasswordDialog(context);
|
||||||
|
|
||||||
|
await SettingsHelper.setDataConservationPolicy(
|
||||||
|
_cachedPassword, _userSettings);
|
||||||
|
|
||||||
|
_key.currentState.refresh();
|
||||||
|
} catch (e, s) {
|
||||||
|
print("Could not update data conservation policy! $e\n$s");
|
||||||
|
showSimpleSnack(
|
||||||
|
context, tr("Failed to update data conservation policy!"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Permanently delete user account
|
/// Permanently delete user account
|
||||||
@ -61,6 +166,98 @@ class _AccountPrivacySettingsState extends State<AccountPrivacySettings> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DataConservationPolicyTile extends SettingsTile {
|
||||||
|
final int value;
|
||||||
|
final String title;
|
||||||
|
final Function(int) onChange;
|
||||||
|
final int minValue;
|
||||||
|
|
||||||
|
const DataConservationPolicyTile({
|
||||||
|
@required this.value,
|
||||||
|
@required this.title,
|
||||||
|
@required this.onChange,
|
||||||
|
@required this.minValue,
|
||||||
|
}) : assert(title != null),
|
||||||
|
assert(onChange != null),
|
||||||
|
assert(minValue != null);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MultiChoicesSettingsTile(
|
||||||
|
title: title,
|
||||||
|
choices: _choices,
|
||||||
|
currentValue: _roundValue,
|
||||||
|
onChanged: onChange,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get _day => 60 * 60 * 24;
|
||||||
|
|
||||||
|
int get _month => _day * 30;
|
||||||
|
|
||||||
|
int get _year => _day * 365;
|
||||||
|
|
||||||
|
int get _roundValue {
|
||||||
|
if (this.value == null) return 0;
|
||||||
|
|
||||||
|
return _choices.firstWhere((element) => element.id >= this.value).id;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MultiChoiceEntry<int>> get _choices => [
|
||||||
|
MultiChoiceEntry(id: 0, title: tr("Never"), hidden: false),
|
||||||
|
MultiChoiceEntry(
|
||||||
|
id: _day * 7,
|
||||||
|
title: tr("7 days"),
|
||||||
|
hidden: _day * 7 < minValue,
|
||||||
|
),
|
||||||
|
MultiChoiceEntry(
|
||||||
|
id: _day * 15,
|
||||||
|
title: tr("15 days"),
|
||||||
|
hidden: _day * 15 < minValue,
|
||||||
|
),
|
||||||
|
MultiChoiceEntry(
|
||||||
|
id: _month,
|
||||||
|
title: tr("1 month"),
|
||||||
|
hidden: _month < minValue,
|
||||||
|
),
|
||||||
|
MultiChoiceEntry(
|
||||||
|
id: _month * 3,
|
||||||
|
title: tr("3 months"),
|
||||||
|
hidden: _month * 3 < minValue,
|
||||||
|
),
|
||||||
|
MultiChoiceEntry(
|
||||||
|
id: _month * 6,
|
||||||
|
title: tr("6 months"),
|
||||||
|
hidden: _month * 6 < minValue,
|
||||||
|
),
|
||||||
|
MultiChoiceEntry(
|
||||||
|
id: _year,
|
||||||
|
title: tr("1 year"),
|
||||||
|
hidden: _year < minValue,
|
||||||
|
),
|
||||||
|
MultiChoiceEntry(
|
||||||
|
id: _year * 2,
|
||||||
|
title: tr("2 years"),
|
||||||
|
hidden: _year * 5 < minValue,
|
||||||
|
),
|
||||||
|
MultiChoiceEntry(
|
||||||
|
id: _year * 5,
|
||||||
|
title: tr("5 years"),
|
||||||
|
hidden: _year * 5 < minValue,
|
||||||
|
),
|
||||||
|
MultiChoiceEntry(
|
||||||
|
id: _year * 10,
|
||||||
|
title: tr("10 years"),
|
||||||
|
hidden: _year * 10 < minValue,
|
||||||
|
),
|
||||||
|
MultiChoiceEntry(
|
||||||
|
id: _year * 50,
|
||||||
|
title: tr("50 years"),
|
||||||
|
hidden: _year * 50 < minValue,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
class _LastChanceDeleteAccountDialog extends StatelessWidget {
|
class _LastChanceDeleteAccountDialog extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import 'package:comunic/helpers/account_helper.dart';
|
import 'package:comunic/helpers/account_helper.dart';
|
||||||
import 'package:comunic/helpers/settings_helper.dart';
|
import 'package:comunic/helpers/settings_helper.dart';
|
||||||
|
import 'package:comunic/helpers/users_helper.dart';
|
||||||
import 'package:comunic/models/security_settings.dart';
|
import 'package:comunic/models/security_settings.dart';
|
||||||
import 'package:comunic/ui/dialogs/input_new_password_dialog.dart';
|
import 'package:comunic/ui/dialogs/input_new_password_dialog.dart';
|
||||||
import 'package:comunic/ui/dialogs/input_user_password_dialog.dart';
|
import 'package:comunic/ui/dialogs/input_user_password_dialog.dart';
|
||||||
import 'package:comunic/ui/widgets/dialogs/auto_sized_dialog_content_widget.dart';
|
import 'package:comunic/ui/widgets/dialogs/auto_sized_dialog_content_widget.dart';
|
||||||
|
import 'package:comunic/ui/widgets/new_password_input_widget.dart';
|
||||||
import 'package:comunic/ui/widgets/settings/header_spacer_section.dart';
|
import 'package:comunic/ui/widgets/settings/header_spacer_section.dart';
|
||||||
|
import 'package:comunic/utils/account_utils.dart';
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:comunic/utils/ui_utils.dart';
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -55,11 +58,21 @@ class _AccountSecuritySettingsScreenState
|
|||||||
/// Change current user password
|
/// Change current user password
|
||||||
void _changePassword() async {
|
void _changePassword() async {
|
||||||
try {
|
try {
|
||||||
|
final currEmail = await AccountHelper.getCurrentAccountEmailAddress();
|
||||||
|
final currUser = await UsersHelper().getSingleWithThrow(userID());
|
||||||
|
|
||||||
final currPassword = await showUserPasswordDialog(context);
|
final currPassword = await showUserPasswordDialog(context);
|
||||||
|
|
||||||
if (currPassword == null) return;
|
if (currPassword == null) return;
|
||||||
|
|
||||||
final newPassword = await showInputNewPassword(context);
|
final newPassword = await showInputNewPassword(
|
||||||
|
context: context,
|
||||||
|
userInfo: UserInfoForPassword(
|
||||||
|
firstName: currUser.firstName,
|
||||||
|
lastName: currUser.lastName,
|
||||||
|
email: currEmail,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (newPassword == null) return;
|
if (newPassword == null) return;
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
|
import 'package:comunic/helpers/account_helper.dart';
|
||||||
import 'package:comunic/helpers/events_helper.dart';
|
import 'package:comunic/helpers/events_helper.dart';
|
||||||
|
import 'package:comunic/helpers/server_config_helper.dart';
|
||||||
|
import 'package:comunic/helpers/version_helper.dart';
|
||||||
import 'package:comunic/helpers/websocket_helper.dart';
|
import 'package:comunic/helpers/websocket_helper.dart';
|
||||||
|
import 'package:comunic/ui/dialogs/deprecation_dialog.dart';
|
||||||
import 'package:comunic/ui/routes/login_route.dart';
|
import 'package:comunic/ui/routes/login_route.dart';
|
||||||
import 'package:comunic/ui/routes/main_route/main_route.dart';
|
import 'package:comunic/ui/routes/main_route/main_route.dart';
|
||||||
import 'package:comunic/ui/routes/main_route/smartphone_route.dart';
|
import 'package:comunic/ui/routes/main_route/smartphone_route.dart';
|
||||||
@ -36,7 +40,10 @@ class _InitializeWidgetState extends SafeState<InitializeWidget> {
|
|||||||
super.listen<InvalidLoginTokensEvent>((ev) => _openLoginPage());
|
super.listen<InvalidLoginTokensEvent>((ev) => _openLoginPage());
|
||||||
|
|
||||||
// Listen to WebSocket close event
|
// Listen to WebSocket close event
|
||||||
super.listen<WSClosedEvent>((e) => _tryConnect());
|
super.listen<WSClosedEvent>((e) {
|
||||||
|
_popToMainRoute();
|
||||||
|
_tryConnect();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -54,6 +61,19 @@ class _InitializeWidgetState extends SafeState<InitializeWidget> {
|
|||||||
/// Try to connect to server
|
/// Try to connect to server
|
||||||
void _tryConnect() async {
|
void _tryConnect() async {
|
||||||
try {
|
try {
|
||||||
|
await ServerConfigurationHelper.ensureLoaded();
|
||||||
|
|
||||||
|
if (ServerConfigurationHelper.config.minSupportedMobileVersion > VersionHelper.version)
|
||||||
|
await showDeprecationDialog(context);
|
||||||
|
|
||||||
|
if (!AccountHelper.isUserIDLoaded) {
|
||||||
|
_popToMainRoute();
|
||||||
|
_openLoginPage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Attempting WebSocket connection...");
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_error = false;
|
_error = false;
|
||||||
});
|
});
|
||||||
@ -65,9 +85,7 @@ class _InitializeWidgetState extends SafeState<InitializeWidget> {
|
|||||||
print("Could not connect to server! $e");
|
print("Could not connect to server! $e");
|
||||||
print(stack);
|
print(stack);
|
||||||
|
|
||||||
// Pop until we reach main route
|
_popToMainRoute();
|
||||||
Navigator.of(context).popUntil((settings) =>
|
|
||||||
ModalRoute.of(context).isCurrent || !ModalRoute.of(context).isActive);
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_error = true;
|
_error = true;
|
||||||
@ -77,7 +95,7 @@ class _InitializeWidgetState extends SafeState<InitializeWidget> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return !_error && WebSocketHelper.isConnected()
|
return (!_error && WebSocketHelper.isConnected())
|
||||||
? (isTablet(context)
|
? (isTablet(context)
|
||||||
? TabletRoute(key: mainControllerKey)
|
? TabletRoute(key: mainControllerKey)
|
||||||
: SmartphoneMainRoute(key: mainControllerKey))
|
: SmartphoneMainRoute(key: mainControllerKey))
|
||||||
@ -110,6 +128,11 @@ class _InitializeWidgetState extends SafeState<InitializeWidget> {
|
|||||||
Spacer(
|
Spacer(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
),
|
),
|
||||||
|
Text(tr("Version %version% - Build %build%", args: {
|
||||||
|
"version": VersionHelper.info.version.toString(),
|
||||||
|
"build": VersionHelper.info.buildNumber.toString()
|
||||||
|
})),
|
||||||
|
Spacer(flex: 1),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -138,4 +161,10 @@ class _InitializeWidgetState extends SafeState<InitializeWidget> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _popToMainRoute() {
|
||||||
|
// Pop until we reach main route
|
||||||
|
Navigator.of(context).popUntil((settings) =>
|
||||||
|
ModalRoute.of(context).isCurrent || !ModalRoute.of(context).isActive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
151
lib/ui/widgets/new_password_input_widget.dart
Normal file
151
lib/ui/widgets/new_password_input_widget.dart
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import 'package:comunic/helpers/server_config_helper.dart';
|
||||||
|
import 'package:comunic/models/server_config.dart';
|
||||||
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// New password input widget
|
||||||
|
///
|
||||||
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
|
class UserInfoForPassword {
|
||||||
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
|
final String email;
|
||||||
|
|
||||||
|
const UserInfoForPassword({
|
||||||
|
@required this.firstName,
|
||||||
|
@required this.lastName,
|
||||||
|
@required this.email,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewPasswordInputWidget extends StatefulWidget {
|
||||||
|
final Widget icon;
|
||||||
|
final VoidCallback onEdited;
|
||||||
|
final VoidCallback onSubmitted;
|
||||||
|
final TextInputAction textInputAction;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
final UserInfoForPassword user;
|
||||||
|
|
||||||
|
const NewPasswordInputWidget({
|
||||||
|
Key key,
|
||||||
|
this.icon,
|
||||||
|
this.onEdited,
|
||||||
|
this.onSubmitted,
|
||||||
|
this.textInputAction,
|
||||||
|
@required this.label,
|
||||||
|
@required this.user,
|
||||||
|
}) : assert(label != null),
|
||||||
|
assert(user != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
NewPasswordInputWidgetState createState() => NewPasswordInputWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewPasswordInputWidgetState extends State<NewPasswordInputWidget> {
|
||||||
|
final TextEditingController _controller = TextEditingController();
|
||||||
|
|
||||||
|
String get value => _controller.text;
|
||||||
|
|
||||||
|
bool get valid => value.isNotEmpty && (_errorMessage ?? "").isEmpty;
|
||||||
|
|
||||||
|
PasswordPolicy get _policy => ServerConfigurationHelper.config.passwordPolicy;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant NewPasswordInputWidget oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => TextField(
|
||||||
|
controller: _controller,
|
||||||
|
obscureText: true,
|
||||||
|
onChanged: (s) => _onChanged(),
|
||||||
|
onSubmitted:
|
||||||
|
widget.onSubmitted == null ? null : (s) => widget.onSubmitted(),
|
||||||
|
textInputAction: widget.textInputAction,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
errorText: _errorMessage,
|
||||||
|
errorMaxLines: 3,
|
||||||
|
icon: widget.icon,
|
||||||
|
labelText: widget.label,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
void _onChanged() {
|
||||||
|
setState(() {});
|
||||||
|
if (widget.onEdited != null) widget.onEdited();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate an error message associated with current password
|
||||||
|
String get _errorMessage {
|
||||||
|
if (value.isEmpty) return null;
|
||||||
|
|
||||||
|
// Mandatory checks
|
||||||
|
if (!_policy.allowMailInPassword &&
|
||||||
|
(widget.user.email ?? "").isNotEmpty &&
|
||||||
|
(widget.user.email.toLowerCase().contains(value.toLowerCase()) ||
|
||||||
|
value.toLowerCase().contains(widget.user.email.toLowerCase()))) {
|
||||||
|
return tr("Your password must not contains part of your email address!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_policy.allowNameInPassword &&
|
||||||
|
(widget.user.firstName ?? "").isNotEmpty &&
|
||||||
|
value.toLowerCase().contains(widget.user.firstName.toLowerCase())) {
|
||||||
|
return tr("Your password must not contains your first name!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_policy.allowNameInPassword &&
|
||||||
|
(widget.user.lastName ?? "").isNotEmpty &&
|
||||||
|
value.toLowerCase().contains(widget.user.lastName.toLowerCase())) {
|
||||||
|
return tr("Your password must not contains your last name!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_policy.minPasswordLength > value.length) {
|
||||||
|
return tr(
|
||||||
|
"Your password must be composed of at least %num% characters!",
|
||||||
|
args: {
|
||||||
|
"num": _policy.minPasswordLength.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Characteristics check
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
if (_hasCharacteristic(RegExp(r'[A-Z]'), _policy.minNumberUpperCaseLetters))
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (_hasCharacteristic(RegExp(r'[a-z]'), _policy.minNumberLowerCaseLetters))
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (_hasCharacteristic(RegExp(r'[0-9]'), _policy.minNumberDigits)) count++;
|
||||||
|
|
||||||
|
if (_hasCharacteristic(
|
||||||
|
RegExp(r'[^A-Za-z0-9]'), _policy.minNumberSpecialCharacters)) count++;
|
||||||
|
|
||||||
|
if (count >= _policy.minCategoriesPresence) return null;
|
||||||
|
|
||||||
|
return tr(
|
||||||
|
"Your password must contains characters of at least %num% of the following categories : %upper% upper case letter, %lower% lowercase letter, %digit% digit, %special% special character.",
|
||||||
|
args: {
|
||||||
|
"num": (_policy.minCategoriesPresence == 4
|
||||||
|
? tr("ALL")
|
||||||
|
: _policy.minCategoriesPresence.toString()),
|
||||||
|
"upper": _policy.minNumberUpperCaseLetters.toString(),
|
||||||
|
"lower": _policy.minNumberLowerCaseLetters.toString(),
|
||||||
|
"digit": _policy.minNumberDigits.toString(),
|
||||||
|
"special": _policy.minNumberSpecialCharacters.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _hasCharacteristic(RegExp exp, int requiredCount) {
|
||||||
|
if (requiredCount < 1) return true;
|
||||||
|
|
||||||
|
return exp.allMatches(value).length >= requiredCount;
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
|
|
||||||
/// Check out whether a password is valid or not
|
/// Check out whether a password is valid or not
|
||||||
bool validatePassword(String s) => s.length > 3;
|
bool legacyValidatePassword(String s) => s.length > 3;
|
||||||
|
|
||||||
|
|
||||||
/// Check out whether a given email address is valid or not
|
/// Check out whether a given email address is valid or not
|
||||||
|
@ -332,7 +332,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.3.0"
|
||||||
package_info:
|
package_info:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info
|
name: package_info
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
@ -630,6 +630,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0-nullsafety.3"
|
version: "2.1.0-nullsafety.3"
|
||||||
|
version:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: version
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
wakelock:
|
wakelock:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -11,7 +11,7 @@ description: Comunic client
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.1.0+4
|
version: 1.1.1+5
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.1.0 <3.0.0"
|
sdk: ">=2.1.0 <3.0.0"
|
||||||
@ -85,6 +85,12 @@ dependencies:
|
|||||||
# Pick any kind of file
|
# Pick any kind of file
|
||||||
file_picker_cross: ^4.2.8
|
file_picker_cross: ^4.2.8
|
||||||
|
|
||||||
|
# Get information about current version
|
||||||
|
package_info: ^0.4.3+4
|
||||||
|
|
||||||
|
# Version manager
|
||||||
|
version: ^1.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
Reference in New Issue
Block a user