refactoring and checkpoint
@@ -1,4 +1,17 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
|
||||||
|
<!-- Permission to vibrate -->
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
<application
|
<application
|
||||||
android:label="afterwork"
|
android:label="afterwork"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
8
android/app/src/main/jni/Android.mk
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE := argon2
|
||||||
|
LOCAL_SRC_FILES := src/argon2.c src/blake2b.c src/core.c src/encoding.c src/ref.c src/thread.c
|
||||||
|
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
5
android/app/src/main/jni/Application.mk
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Indique à NDK les ABI (Application Binary Interfaces) pour lesquelles compiler
|
||||||
|
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
|
||||||
|
|
||||||
|
# Indique la version de la plateforme Android minimum supportée
|
||||||
|
APP_PLATFORM := android-21
|
||||||
437
android/app/src/main/jni/argon2.h
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARGON2_H
|
||||||
|
#define ARGON2_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Symbols visibility control */
|
||||||
|
#ifdef A2_VISCTL
|
||||||
|
#define ARGON2_PUBLIC __attribute__((visibility("default")))
|
||||||
|
#define ARGON2_LOCAL __attribute__ ((visibility ("hidden")))
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
#define ARGON2_PUBLIC __declspec(dllexport)
|
||||||
|
#define ARGON2_LOCAL
|
||||||
|
#else
|
||||||
|
#define ARGON2_PUBLIC
|
||||||
|
#define ARGON2_LOCAL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Argon2 input parameter restrictions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Minimum and maximum number of lanes (degree of parallelism) */
|
||||||
|
#define ARGON2_MIN_LANES UINT32_C(1)
|
||||||
|
#define ARGON2_MAX_LANES UINT32_C(0xFFFFFF)
|
||||||
|
|
||||||
|
/* Minimum and maximum number of threads */
|
||||||
|
#define ARGON2_MIN_THREADS UINT32_C(1)
|
||||||
|
#define ARGON2_MAX_THREADS UINT32_C(0xFFFFFF)
|
||||||
|
|
||||||
|
/* Number of synchronization points between lanes per pass */
|
||||||
|
#define ARGON2_SYNC_POINTS UINT32_C(4)
|
||||||
|
|
||||||
|
/* Minimum and maximum digest size in bytes */
|
||||||
|
#define ARGON2_MIN_OUTLEN UINT32_C(4)
|
||||||
|
#define ARGON2_MAX_OUTLEN UINT32_C(0xFFFFFFFF)
|
||||||
|
|
||||||
|
/* Minimum and maximum number of memory blocks (each of BLOCK_SIZE bytes) */
|
||||||
|
#define ARGON2_MIN_MEMORY (2 * ARGON2_SYNC_POINTS) /* 2 blocks per slice */
|
||||||
|
|
||||||
|
#define ARGON2_MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
/* Max memory size is addressing-space/2, topping at 2^32 blocks (4 TB) */
|
||||||
|
#define ARGON2_MAX_MEMORY_BITS \
|
||||||
|
ARGON2_MIN(UINT32_C(32), (sizeof(void *) * CHAR_BIT - 10 - 1))
|
||||||
|
#define ARGON2_MAX_MEMORY \
|
||||||
|
ARGON2_MIN(UINT32_C(0xFFFFFFFF), UINT64_C(1) << ARGON2_MAX_MEMORY_BITS)
|
||||||
|
|
||||||
|
/* Minimum and maximum number of passes */
|
||||||
|
#define ARGON2_MIN_TIME UINT32_C(1)
|
||||||
|
#define ARGON2_MAX_TIME UINT32_C(0xFFFFFFFF)
|
||||||
|
|
||||||
|
/* Minimum and maximum password length in bytes */
|
||||||
|
#define ARGON2_MIN_PWD_LENGTH UINT32_C(0)
|
||||||
|
#define ARGON2_MAX_PWD_LENGTH UINT32_C(0xFFFFFFFF)
|
||||||
|
|
||||||
|
/* Minimum and maximum associated data length in bytes */
|
||||||
|
#define ARGON2_MIN_AD_LENGTH UINT32_C(0)
|
||||||
|
#define ARGON2_MAX_AD_LENGTH UINT32_C(0xFFFFFFFF)
|
||||||
|
|
||||||
|
/* Minimum and maximum salt length in bytes */
|
||||||
|
#define ARGON2_MIN_SALT_LENGTH UINT32_C(8)
|
||||||
|
#define ARGON2_MAX_SALT_LENGTH UINT32_C(0xFFFFFFFF)
|
||||||
|
|
||||||
|
/* Minimum and maximum key length in bytes */
|
||||||
|
#define ARGON2_MIN_SECRET UINT32_C(0)
|
||||||
|
#define ARGON2_MAX_SECRET UINT32_C(0xFFFFFFFF)
|
||||||
|
|
||||||
|
/* Flags to determine which fields are securely wiped (default = no wipe). */
|
||||||
|
#define ARGON2_DEFAULT_FLAGS UINT32_C(0)
|
||||||
|
#define ARGON2_FLAG_CLEAR_PASSWORD (UINT32_C(1) << 0)
|
||||||
|
#define ARGON2_FLAG_CLEAR_SECRET (UINT32_C(1) << 1)
|
||||||
|
|
||||||
|
/* Global flag to determine if we are wiping internal memory buffers. This flag
|
||||||
|
* is defined in core.c and defaults to 1 (wipe internal memory). */
|
||||||
|
extern int FLAG_clear_internal_memory;
|
||||||
|
|
||||||
|
/* Error codes */
|
||||||
|
typedef enum Argon2_ErrorCodes {
|
||||||
|
ARGON2_OK = 0,
|
||||||
|
|
||||||
|
ARGON2_OUTPUT_PTR_NULL = -1,
|
||||||
|
|
||||||
|
ARGON2_OUTPUT_TOO_SHORT = -2,
|
||||||
|
ARGON2_OUTPUT_TOO_LONG = -3,
|
||||||
|
|
||||||
|
ARGON2_PWD_TOO_SHORT = -4,
|
||||||
|
ARGON2_PWD_TOO_LONG = -5,
|
||||||
|
|
||||||
|
ARGON2_SALT_TOO_SHORT = -6,
|
||||||
|
ARGON2_SALT_TOO_LONG = -7,
|
||||||
|
|
||||||
|
ARGON2_AD_TOO_SHORT = -8,
|
||||||
|
ARGON2_AD_TOO_LONG = -9,
|
||||||
|
|
||||||
|
ARGON2_SECRET_TOO_SHORT = -10,
|
||||||
|
ARGON2_SECRET_TOO_LONG = -11,
|
||||||
|
|
||||||
|
ARGON2_TIME_TOO_SMALL = -12,
|
||||||
|
ARGON2_TIME_TOO_LARGE = -13,
|
||||||
|
|
||||||
|
ARGON2_MEMORY_TOO_LITTLE = -14,
|
||||||
|
ARGON2_MEMORY_TOO_MUCH = -15,
|
||||||
|
|
||||||
|
ARGON2_LANES_TOO_FEW = -16,
|
||||||
|
ARGON2_LANES_TOO_MANY = -17,
|
||||||
|
|
||||||
|
ARGON2_PWD_PTR_MISMATCH = -18, /* NULL ptr with non-zero length */
|
||||||
|
ARGON2_SALT_PTR_MISMATCH = -19, /* NULL ptr with non-zero length */
|
||||||
|
ARGON2_SECRET_PTR_MISMATCH = -20, /* NULL ptr with non-zero length */
|
||||||
|
ARGON2_AD_PTR_MISMATCH = -21, /* NULL ptr with non-zero length */
|
||||||
|
|
||||||
|
ARGON2_MEMORY_ALLOCATION_ERROR = -22,
|
||||||
|
|
||||||
|
ARGON2_FREE_MEMORY_CBK_NULL = -23,
|
||||||
|
ARGON2_ALLOCATE_MEMORY_CBK_NULL = -24,
|
||||||
|
|
||||||
|
ARGON2_INCORRECT_PARAMETER = -25,
|
||||||
|
ARGON2_INCORRECT_TYPE = -26,
|
||||||
|
|
||||||
|
ARGON2_OUT_PTR_MISMATCH = -27,
|
||||||
|
|
||||||
|
ARGON2_THREADS_TOO_FEW = -28,
|
||||||
|
ARGON2_THREADS_TOO_MANY = -29,
|
||||||
|
|
||||||
|
ARGON2_MISSING_ARGS = -30,
|
||||||
|
|
||||||
|
ARGON2_ENCODING_FAIL = -31,
|
||||||
|
|
||||||
|
ARGON2_DECODING_FAIL = -32,
|
||||||
|
|
||||||
|
ARGON2_THREAD_FAIL = -33,
|
||||||
|
|
||||||
|
ARGON2_DECODING_LENGTH_FAIL = -34,
|
||||||
|
|
||||||
|
ARGON2_VERIFY_MISMATCH = -35
|
||||||
|
} argon2_error_codes;
|
||||||
|
|
||||||
|
/* Memory allocator types --- for external allocation */
|
||||||
|
typedef int (*allocate_fptr)(uint8_t **memory, size_t bytes_to_allocate);
|
||||||
|
typedef void (*deallocate_fptr)(uint8_t *memory, size_t bytes_to_allocate);
|
||||||
|
|
||||||
|
/* Argon2 external data structures */
|
||||||
|
|
||||||
|
/*
|
||||||
|
*****
|
||||||
|
* Context: structure to hold Argon2 inputs:
|
||||||
|
* output array and its length,
|
||||||
|
* password and its length,
|
||||||
|
* salt and its length,
|
||||||
|
* secret and its length,
|
||||||
|
* associated data and its length,
|
||||||
|
* number of passes, amount of used memory (in KBytes, can be rounded up a bit)
|
||||||
|
* number of parallel threads that will be run.
|
||||||
|
* All the parameters above affect the output hash value.
|
||||||
|
* Additionally, two function pointers can be provided to allocate and
|
||||||
|
* deallocate the memory (if NULL, memory will be allocated internally).
|
||||||
|
* Also, three flags indicate whether to erase password, secret as soon as they
|
||||||
|
* are pre-hashed (and thus not needed anymore), and the entire memory
|
||||||
|
*****
|
||||||
|
* Simplest situation: you have output array out[8], password is stored in
|
||||||
|
* pwd[32], salt is stored in salt[16], you do not have keys nor associated
|
||||||
|
* data. You need to spend 1 GB of RAM and you run 5 passes of Argon2d with
|
||||||
|
* 4 parallel lanes.
|
||||||
|
* You want to erase the password, but you're OK with last pass not being
|
||||||
|
* erased. You want to use the default memory allocator.
|
||||||
|
* Then you initialize:
|
||||||
|
Argon2_Context(out,8,pwd,32,salt,16,NULL,0,NULL,0,5,1<<20,4,4,NULL,NULL,true,false,false,false)
|
||||||
|
*/
|
||||||
|
typedef struct Argon2_Context {
|
||||||
|
uint8_t *out; /* output array */
|
||||||
|
uint32_t outlen; /* digest length */
|
||||||
|
|
||||||
|
uint8_t *pwd; /* password array */
|
||||||
|
uint32_t pwdlen; /* password length */
|
||||||
|
|
||||||
|
uint8_t *salt; /* salt array */
|
||||||
|
uint32_t saltlen; /* salt length */
|
||||||
|
|
||||||
|
uint8_t *secret; /* key array */
|
||||||
|
uint32_t secretlen; /* key length */
|
||||||
|
|
||||||
|
uint8_t *ad; /* associated data array */
|
||||||
|
uint32_t adlen; /* associated data length */
|
||||||
|
|
||||||
|
uint32_t t_cost; /* number of passes */
|
||||||
|
uint32_t m_cost; /* amount of memory requested (KB) */
|
||||||
|
uint32_t lanes; /* number of lanes */
|
||||||
|
uint32_t threads; /* maximum number of threads */
|
||||||
|
|
||||||
|
uint32_t version; /* version number */
|
||||||
|
|
||||||
|
allocate_fptr allocate_cbk; /* pointer to memory allocator */
|
||||||
|
deallocate_fptr free_cbk; /* pointer to memory deallocator */
|
||||||
|
|
||||||
|
uint32_t flags; /* array of bool options */
|
||||||
|
} argon2_context;
|
||||||
|
|
||||||
|
/* Argon2 primitive type */
|
||||||
|
typedef enum Argon2_type {
|
||||||
|
Argon2_d = 0,
|
||||||
|
Argon2_i = 1,
|
||||||
|
Argon2_id = 2
|
||||||
|
} argon2_type;
|
||||||
|
|
||||||
|
/* Version of the algorithm */
|
||||||
|
typedef enum Argon2_version {
|
||||||
|
ARGON2_VERSION_10 = 0x10,
|
||||||
|
ARGON2_VERSION_13 = 0x13,
|
||||||
|
ARGON2_VERSION_NUMBER = ARGON2_VERSION_13
|
||||||
|
} argon2_version;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function that gives the string representation of an argon2_type.
|
||||||
|
* @param type The argon2_type that we want the string for
|
||||||
|
* @param uppercase Whether the string should have the first letter uppercase
|
||||||
|
* @return NULL if invalid type, otherwise the string representation.
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC const char *argon2_type2string(argon2_type type, int uppercase);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function that performs memory-hard hashing with certain degree of parallelism
|
||||||
|
* @param context Pointer to the Argon2 internal structure
|
||||||
|
* @return Error code if smth is wrong, ARGON2_OK otherwise
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC int argon2_ctx(argon2_context *context, argon2_type type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hashes a password with Argon2i, producing an encoded hash
|
||||||
|
* @param t_cost Number of iterations
|
||||||
|
* @param m_cost Sets memory usage to m_cost kibibytes
|
||||||
|
* @param parallelism Number of threads and compute lanes
|
||||||
|
* @param pwd Pointer to password
|
||||||
|
* @param pwdlen Password size in bytes
|
||||||
|
* @param salt Pointer to salt
|
||||||
|
* @param saltlen Salt size in bytes
|
||||||
|
* @param hashlen Desired length of the hash in bytes
|
||||||
|
* @param encoded Buffer where to write the encoded hash
|
||||||
|
* @param encodedlen Size of the buffer (thus max size of the encoded hash)
|
||||||
|
* @pre Different parallelism levels will give different results
|
||||||
|
* @pre Returns ARGON2_OK if successful
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC int argon2i_hash_encoded(const uint32_t t_cost,
|
||||||
|
const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism,
|
||||||
|
const void *pwd, const size_t pwdlen,
|
||||||
|
const void *salt, const size_t saltlen,
|
||||||
|
const size_t hashlen, char *encoded,
|
||||||
|
const size_t encodedlen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hashes a password with Argon2i, producing a raw hash at @hash
|
||||||
|
* @param t_cost Number of iterations
|
||||||
|
* @param m_cost Sets memory usage to m_cost kibibytes
|
||||||
|
* @param parallelism Number of threads and compute lanes
|
||||||
|
* @param pwd Pointer to password
|
||||||
|
* @param pwdlen Password size in bytes
|
||||||
|
* @param salt Pointer to salt
|
||||||
|
* @param saltlen Salt size in bytes
|
||||||
|
* @param hash Buffer where to write the raw hash - updated by the function
|
||||||
|
* @param hashlen Desired length of the hash in bytes
|
||||||
|
* @pre Different parallelism levels will give different results
|
||||||
|
* @pre Returns ARGON2_OK if successful
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC int argon2i_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt,
|
||||||
|
const size_t saltlen, void *hash,
|
||||||
|
const size_t hashlen);
|
||||||
|
|
||||||
|
ARGON2_PUBLIC int argon2d_hash_encoded(const uint32_t t_cost,
|
||||||
|
const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism,
|
||||||
|
const void *pwd, const size_t pwdlen,
|
||||||
|
const void *salt, const size_t saltlen,
|
||||||
|
const size_t hashlen, char *encoded,
|
||||||
|
const size_t encodedlen);
|
||||||
|
|
||||||
|
ARGON2_PUBLIC int argon2d_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt,
|
||||||
|
const size_t saltlen, void *hash,
|
||||||
|
const size_t hashlen);
|
||||||
|
|
||||||
|
ARGON2_PUBLIC int argon2id_hash_encoded(const uint32_t t_cost,
|
||||||
|
const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism,
|
||||||
|
const void *pwd, const size_t pwdlen,
|
||||||
|
const void *salt, const size_t saltlen,
|
||||||
|
const size_t hashlen, char *encoded,
|
||||||
|
const size_t encodedlen);
|
||||||
|
|
||||||
|
ARGON2_PUBLIC int argon2id_hash_raw(const uint32_t t_cost,
|
||||||
|
const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt,
|
||||||
|
const size_t saltlen, void *hash,
|
||||||
|
const size_t hashlen);
|
||||||
|
|
||||||
|
/* generic function underlying the above ones */
|
||||||
|
ARGON2_PUBLIC int argon2_hash(const uint32_t t_cost, const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt,
|
||||||
|
const size_t saltlen, void *hash,
|
||||||
|
const size_t hashlen, char *encoded,
|
||||||
|
const size_t encodedlen, argon2_type type,
|
||||||
|
const uint32_t version);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a password against an encoded string
|
||||||
|
* Encoded string is restricted as in validate_inputs()
|
||||||
|
* @param encoded String encoding parameters, salt, hash
|
||||||
|
* @param pwd Pointer to password
|
||||||
|
* @pre Returns ARGON2_OK if successful
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC int argon2i_verify(const char *encoded, const void *pwd,
|
||||||
|
const size_t pwdlen);
|
||||||
|
|
||||||
|
ARGON2_PUBLIC int argon2d_verify(const char *encoded, const void *pwd,
|
||||||
|
const size_t pwdlen);
|
||||||
|
|
||||||
|
ARGON2_PUBLIC int argon2id_verify(const char *encoded, const void *pwd,
|
||||||
|
const size_t pwdlen);
|
||||||
|
|
||||||
|
/* generic function underlying the above ones */
|
||||||
|
ARGON2_PUBLIC int argon2_verify(const char *encoded, const void *pwd,
|
||||||
|
const size_t pwdlen, argon2_type type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Argon2d: Version of Argon2 that picks memory blocks depending
|
||||||
|
* on the password and salt. Only for side-channel-free
|
||||||
|
* environment!!
|
||||||
|
*****
|
||||||
|
* @param context Pointer to current Argon2 context
|
||||||
|
* @return Zero if successful, a non zero error code otherwise
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC int argon2d_ctx(argon2_context *context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Argon2i: Version of Argon2 that picks memory blocks
|
||||||
|
* independent on the password and salt. Good for side-channels,
|
||||||
|
* but worse w.r.t. tradeoff attacks if only one pass is used.
|
||||||
|
*****
|
||||||
|
* @param context Pointer to current Argon2 context
|
||||||
|
* @return Zero if successful, a non zero error code otherwise
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC int argon2i_ctx(argon2_context *context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Argon2id: Version of Argon2 where the first half-pass over memory is
|
||||||
|
* password-independent, the rest are password-dependent (on the password and
|
||||||
|
* salt). OK against side channels (they reduce to 1/2-pass Argon2i), and
|
||||||
|
* better with w.r.t. tradeoff attacks (similar to Argon2d).
|
||||||
|
*****
|
||||||
|
* @param context Pointer to current Argon2 context
|
||||||
|
* @return Zero if successful, a non zero error code otherwise
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC int argon2id_ctx(argon2_context *context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if a given password is correct for Argon2d hashing
|
||||||
|
* @param context Pointer to current Argon2 context
|
||||||
|
* @param hash The password hash to verify. The length of the hash is
|
||||||
|
* specified by the context outlen member
|
||||||
|
* @return Zero if successful, a non zero error code otherwise
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC int argon2d_verify_ctx(argon2_context *context, const char *hash);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if a given password is correct for Argon2i hashing
|
||||||
|
* @param context Pointer to current Argon2 context
|
||||||
|
* @param hash The password hash to verify. The length of the hash is
|
||||||
|
* specified by the context outlen member
|
||||||
|
* @return Zero if successful, a non zero error code otherwise
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC int argon2i_verify_ctx(argon2_context *context, const char *hash);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if a given password is correct for Argon2id hashing
|
||||||
|
* @param context Pointer to current Argon2 context
|
||||||
|
* @param hash The password hash to verify. The length of the hash is
|
||||||
|
* specified by the context outlen member
|
||||||
|
* @return Zero if successful, a non zero error code otherwise
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC int argon2id_verify_ctx(argon2_context *context,
|
||||||
|
const char *hash);
|
||||||
|
|
||||||
|
/* generic function underlying the above ones */
|
||||||
|
ARGON2_PUBLIC int argon2_verify_ctx(argon2_context *context, const char *hash,
|
||||||
|
argon2_type type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated error message for given error code
|
||||||
|
* @return The error message associated with the given error code
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC const char *argon2_error_message(int error_code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoded hash length for the given input parameters
|
||||||
|
* @param t_cost Number of iterations
|
||||||
|
* @param m_cost Memory usage in kibibytes
|
||||||
|
* @param parallelism Number of threads; used to compute lanes
|
||||||
|
* @param saltlen Salt size in bytes
|
||||||
|
* @param hashlen Hash size in bytes
|
||||||
|
* @param type The argon2_type that we want the encoded length for
|
||||||
|
* @return The encoded hash length in bytes
|
||||||
|
*/
|
||||||
|
ARGON2_PUBLIC size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost,
|
||||||
|
uint32_t parallelism, uint32_t saltlen,
|
||||||
|
uint32_t hashlen, argon2_type type);
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
156
android/app/src/main/jni/blake2-impl.h
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PORTABLE_BLAKE2_IMPL_H
|
||||||
|
#define PORTABLE_BLAKE2_IMPL_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define BLAKE2_INLINE __inline
|
||||||
|
#elif defined(__GNUC__) || defined(__clang__)
|
||||||
|
#define BLAKE2_INLINE __inline__
|
||||||
|
#else
|
||||||
|
#define BLAKE2_INLINE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Argon2 Team - Begin Code */
|
||||||
|
/*
|
||||||
|
Not an exhaustive list, but should cover the majority of modern platforms
|
||||||
|
Additionally, the code will always be correct---this is only a performance
|
||||||
|
tweak.
|
||||||
|
*/
|
||||||
|
#if (defined(__BYTE_ORDER__) && \
|
||||||
|
(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) || \
|
||||||
|
defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__MIPSEL__) || \
|
||||||
|
defined(__AARCH64EL__) || defined(__amd64__) || defined(__i386__) || \
|
||||||
|
defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) || \
|
||||||
|
defined(_M_ARM)
|
||||||
|
#define NATIVE_LITTLE_ENDIAN
|
||||||
|
#endif
|
||||||
|
/* Argon2 Team - End Code */
|
||||||
|
|
||||||
|
static BLAKE2_INLINE uint32_t load32(const void *src) {
|
||||||
|
#if defined(NATIVE_LITTLE_ENDIAN)
|
||||||
|
uint32_t w;
|
||||||
|
memcpy(&w, src, sizeof w);
|
||||||
|
return w;
|
||||||
|
#else
|
||||||
|
const uint8_t *p = (const uint8_t *)src;
|
||||||
|
uint32_t w = *p++;
|
||||||
|
w |= (uint32_t)(*p++) << 8;
|
||||||
|
w |= (uint32_t)(*p++) << 16;
|
||||||
|
w |= (uint32_t)(*p++) << 24;
|
||||||
|
return w;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE uint64_t load64(const void *src) {
|
||||||
|
#if defined(NATIVE_LITTLE_ENDIAN)
|
||||||
|
uint64_t w;
|
||||||
|
memcpy(&w, src, sizeof w);
|
||||||
|
return w;
|
||||||
|
#else
|
||||||
|
const uint8_t *p = (const uint8_t *)src;
|
||||||
|
uint64_t w = *p++;
|
||||||
|
w |= (uint64_t)(*p++) << 8;
|
||||||
|
w |= (uint64_t)(*p++) << 16;
|
||||||
|
w |= (uint64_t)(*p++) << 24;
|
||||||
|
w |= (uint64_t)(*p++) << 32;
|
||||||
|
w |= (uint64_t)(*p++) << 40;
|
||||||
|
w |= (uint64_t)(*p++) << 48;
|
||||||
|
w |= (uint64_t)(*p++) << 56;
|
||||||
|
return w;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void store32(void *dst, uint32_t w) {
|
||||||
|
#if defined(NATIVE_LITTLE_ENDIAN)
|
||||||
|
memcpy(dst, &w, sizeof w);
|
||||||
|
#else
|
||||||
|
uint8_t *p = (uint8_t *)dst;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void store64(void *dst, uint64_t w) {
|
||||||
|
#if defined(NATIVE_LITTLE_ENDIAN)
|
||||||
|
memcpy(dst, &w, sizeof w);
|
||||||
|
#else
|
||||||
|
uint8_t *p = (uint8_t *)dst;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE uint64_t load48(const void *src) {
|
||||||
|
const uint8_t *p = (const uint8_t *)src;
|
||||||
|
uint64_t w = *p++;
|
||||||
|
w |= (uint64_t)(*p++) << 8;
|
||||||
|
w |= (uint64_t)(*p++) << 16;
|
||||||
|
w |= (uint64_t)(*p++) << 24;
|
||||||
|
w |= (uint64_t)(*p++) << 32;
|
||||||
|
w |= (uint64_t)(*p++) << 40;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void store48(void *dst, uint64_t w) {
|
||||||
|
uint8_t *p = (uint8_t *)dst;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE uint32_t rotr32(const uint32_t w, const unsigned c) {
|
||||||
|
return (w >> c) | (w << (32 - c));
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE uint64_t rotr64(const uint64_t w, const unsigned c) {
|
||||||
|
return (w >> c) | (w << (64 - c));
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_internal_memory(void *v, size_t n);
|
||||||
|
|
||||||
|
#endif
|
||||||
89
android/app/src/main/jni/blake2.h
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PORTABLE_BLAKE2_H
|
||||||
|
#define PORTABLE_BLAKE2_H
|
||||||
|
|
||||||
|
#include <argon2.h>
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum blake2b_constant {
|
||||||
|
BLAKE2B_BLOCKBYTES = 128,
|
||||||
|
BLAKE2B_OUTBYTES = 64,
|
||||||
|
BLAKE2B_KEYBYTES = 64,
|
||||||
|
BLAKE2B_SALTBYTES = 16,
|
||||||
|
BLAKE2B_PERSONALBYTES = 16
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct __blake2b_param {
|
||||||
|
uint8_t digest_length; /* 1 */
|
||||||
|
uint8_t key_length; /* 2 */
|
||||||
|
uint8_t fanout; /* 3 */
|
||||||
|
uint8_t depth; /* 4 */
|
||||||
|
uint32_t leaf_length; /* 8 */
|
||||||
|
uint64_t node_offset; /* 16 */
|
||||||
|
uint8_t node_depth; /* 17 */
|
||||||
|
uint8_t inner_length; /* 18 */
|
||||||
|
uint8_t reserved[14]; /* 32 */
|
||||||
|
uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */
|
||||||
|
uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */
|
||||||
|
} blake2b_param;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
typedef struct __blake2b_state {
|
||||||
|
uint64_t h[8];
|
||||||
|
uint64_t t[2];
|
||||||
|
uint64_t f[2];
|
||||||
|
uint8_t buf[BLAKE2B_BLOCKBYTES];
|
||||||
|
unsigned buflen;
|
||||||
|
unsigned outlen;
|
||||||
|
uint8_t last_node;
|
||||||
|
} blake2b_state;
|
||||||
|
|
||||||
|
/* Ensure param structs have not been wrongly padded */
|
||||||
|
/* Poor man's static_assert */
|
||||||
|
enum {
|
||||||
|
blake2_size_check_0 = 1 / !!(CHAR_BIT == 8),
|
||||||
|
blake2_size_check_2 =
|
||||||
|
1 / !!(sizeof(blake2b_param) == sizeof(uint64_t) * CHAR_BIT)
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Streaming API */
|
||||||
|
ARGON2_LOCAL int blake2b_init(blake2b_state *S, size_t outlen);
|
||||||
|
ARGON2_LOCAL int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key,
|
||||||
|
size_t keylen);
|
||||||
|
ARGON2_LOCAL int blake2b_init_param(blake2b_state *S, const blake2b_param *P);
|
||||||
|
ARGON2_LOCAL int blake2b_update(blake2b_state *S, const void *in, size_t inlen);
|
||||||
|
ARGON2_LOCAL int blake2b_final(blake2b_state *S, void *out, size_t outlen);
|
||||||
|
|
||||||
|
/* Simple API */
|
||||||
|
ARGON2_LOCAL int blake2b(void *out, size_t outlen, const void *in, size_t inlen,
|
||||||
|
const void *key, size_t keylen);
|
||||||
|
|
||||||
|
/* Argon2 Team - Begin Code */
|
||||||
|
ARGON2_LOCAL int blake2b_long(void *out, size_t outlen, const void *in, size_t inlen);
|
||||||
|
/* Argon2 Team - End Code */
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
228
android/app/src/main/jni/core.h
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARGON2_CORE_H
|
||||||
|
#define ARGON2_CORE_H
|
||||||
|
|
||||||
|
#include "argon2.h"
|
||||||
|
|
||||||
|
#define CONST_CAST(x) (x)(uintptr_t)
|
||||||
|
|
||||||
|
/**********************Argon2 internal constants*******************************/
|
||||||
|
|
||||||
|
enum argon2_core_constants {
|
||||||
|
/* Memory block size in bytes */
|
||||||
|
ARGON2_BLOCK_SIZE = 1024,
|
||||||
|
ARGON2_QWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 8,
|
||||||
|
ARGON2_OWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 16,
|
||||||
|
ARGON2_HWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 32,
|
||||||
|
ARGON2_512BIT_WORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 64,
|
||||||
|
|
||||||
|
/* Number of pseudo-random values generated by one call to Blake in Argon2i
|
||||||
|
to
|
||||||
|
generate reference block positions */
|
||||||
|
ARGON2_ADDRESSES_IN_BLOCK = 128,
|
||||||
|
|
||||||
|
/* Pre-hashing digest length and its extension*/
|
||||||
|
ARGON2_PREHASH_DIGEST_LENGTH = 64,
|
||||||
|
ARGON2_PREHASH_SEED_LENGTH = 72
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************************Argon2 internal data types***********************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure for the (1KB) memory block implemented as 128 64-bit words.
|
||||||
|
* Memory blocks can be copied, XORed. Internal words can be accessed by [] (no
|
||||||
|
* bounds checking).
|
||||||
|
*/
|
||||||
|
typedef struct block_ { uint64_t v[ARGON2_QWORDS_IN_BLOCK]; } block;
|
||||||
|
|
||||||
|
/*****************Functions that work with the block******************/
|
||||||
|
|
||||||
|
/* Initialize each byte of the block with @in */
|
||||||
|
void init_block_value(block *b, uint8_t in);
|
||||||
|
|
||||||
|
/* Copy block @src to block @dst */
|
||||||
|
void copy_block(block *dst, const block *src);
|
||||||
|
|
||||||
|
/* XOR @src onto @dst bytewise */
|
||||||
|
void xor_block(block *dst, const block *src);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Argon2 instance: memory pointer, number of passes, amount of memory, type,
|
||||||
|
* and derived values.
|
||||||
|
* Used to evaluate the number and location of blocks to construct in each
|
||||||
|
* thread
|
||||||
|
*/
|
||||||
|
typedef struct Argon2_instance_t {
|
||||||
|
block *memory; /* Memory pointer */
|
||||||
|
uint32_t version;
|
||||||
|
uint32_t passes; /* Number of passes */
|
||||||
|
uint32_t memory_blocks; /* Number of blocks in memory */
|
||||||
|
uint32_t segment_length;
|
||||||
|
uint32_t lane_length;
|
||||||
|
uint32_t lanes;
|
||||||
|
uint32_t threads;
|
||||||
|
argon2_type type;
|
||||||
|
int print_internals; /* whether to print the memory blocks */
|
||||||
|
argon2_context *context_ptr; /* points back to original context */
|
||||||
|
} argon2_instance_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Argon2 position: where we construct the block right now. Used to distribute
|
||||||
|
* work between threads.
|
||||||
|
*/
|
||||||
|
typedef struct Argon2_position_t {
|
||||||
|
uint32_t pass;
|
||||||
|
uint32_t lane;
|
||||||
|
uint8_t slice;
|
||||||
|
uint32_t index;
|
||||||
|
} argon2_position_t;
|
||||||
|
|
||||||
|
/*Struct that holds the inputs for thread handling FillSegment*/
|
||||||
|
typedef struct Argon2_thread_data {
|
||||||
|
argon2_instance_t *instance_ptr;
|
||||||
|
argon2_position_t pos;
|
||||||
|
} argon2_thread_data;
|
||||||
|
|
||||||
|
/*************************Argon2 core functions********************************/
|
||||||
|
|
||||||
|
/* Allocates memory to the given pointer, uses the appropriate allocator as
|
||||||
|
* specified in the context. Total allocated memory is num*size.
|
||||||
|
* @param context argon2_context which specifies the allocator
|
||||||
|
* @param memory pointer to the pointer to the memory
|
||||||
|
* @param size the size in bytes for each element to be allocated
|
||||||
|
* @param num the number of elements to be allocated
|
||||||
|
* @return ARGON2_OK if @memory is a valid pointer and memory is allocated
|
||||||
|
*/
|
||||||
|
int allocate_memory(const argon2_context *context, uint8_t **memory,
|
||||||
|
size_t num, size_t size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Frees memory at the given pointer, uses the appropriate deallocator as
|
||||||
|
* specified in the context. Also cleans the memory using clear_internal_memory.
|
||||||
|
* @param context argon2_context which specifies the deallocator
|
||||||
|
* @param memory pointer to buffer to be freed
|
||||||
|
* @param size the size in bytes for each element to be deallocated
|
||||||
|
* @param num the number of elements to be deallocated
|
||||||
|
*/
|
||||||
|
void free_memory(const argon2_context *context, uint8_t *memory,
|
||||||
|
size_t num, size_t size);
|
||||||
|
|
||||||
|
/* Function that securely cleans the memory. This ignores any flags set
|
||||||
|
* regarding clearing memory. Usually one just calls clear_internal_memory.
|
||||||
|
* @param mem Pointer to the memory
|
||||||
|
* @param s Memory size in bytes
|
||||||
|
*/
|
||||||
|
void secure_wipe_memory(void *v, size_t n);
|
||||||
|
|
||||||
|
/* Function that securely clears the memory if FLAG_clear_internal_memory is
|
||||||
|
* set. If the flag isn't set, this function does nothing.
|
||||||
|
* @param mem Pointer to the memory
|
||||||
|
* @param s Memory size in bytes
|
||||||
|
*/
|
||||||
|
void clear_internal_memory(void *v, size_t n);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Computes absolute position of reference block in the lane following a skewed
|
||||||
|
* distribution and using a pseudo-random value as input
|
||||||
|
* @param instance Pointer to the current instance
|
||||||
|
* @param position Pointer to the current position
|
||||||
|
* @param pseudo_rand 32-bit pseudo-random value used to determine the position
|
||||||
|
* @param same_lane Indicates if the block will be taken from the current lane.
|
||||||
|
* If so we can reference the current segment
|
||||||
|
* @pre All pointers must be valid
|
||||||
|
*/
|
||||||
|
uint32_t index_alpha(const argon2_instance_t *instance,
|
||||||
|
const argon2_position_t *position, uint32_t pseudo_rand,
|
||||||
|
int same_lane);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function that validates all inputs against predefined restrictions and return
|
||||||
|
* an error code
|
||||||
|
* @param context Pointer to current Argon2 context
|
||||||
|
* @return ARGON2_OK if everything is all right, otherwise one of error codes
|
||||||
|
* (all defined in <argon2.h>
|
||||||
|
*/
|
||||||
|
int validate_inputs(const argon2_context *context);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hashes all the inputs into @a blockhash[PREHASH_DIGEST_LENGTH], clears
|
||||||
|
* password and secret if needed
|
||||||
|
* @param context Pointer to the Argon2 internal structure containing memory
|
||||||
|
* pointer, and parameters for time and space requirements.
|
||||||
|
* @param blockhash Buffer for pre-hashing digest
|
||||||
|
* @param type Argon2 type
|
||||||
|
* @pre @a blockhash must have at least @a PREHASH_DIGEST_LENGTH bytes
|
||||||
|
* allocated
|
||||||
|
*/
|
||||||
|
void initial_hash(uint8_t *blockhash, argon2_context *context,
|
||||||
|
argon2_type type);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function creates first 2 blocks per lane
|
||||||
|
* @param instance Pointer to the current instance
|
||||||
|
* @param blockhash Pointer to the pre-hashing digest
|
||||||
|
* @pre blockhash must point to @a PREHASH_SEED_LENGTH allocated values
|
||||||
|
*/
|
||||||
|
void fill_first_blocks(uint8_t *blockhash, const argon2_instance_t *instance);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function allocates memory, hashes the inputs with Blake, and creates first
|
||||||
|
* two blocks. Returns the pointer to the main memory with 2 blocks per lane
|
||||||
|
* initialized
|
||||||
|
* @param context Pointer to the Argon2 internal structure containing memory
|
||||||
|
* pointer, and parameters for time and space requirements.
|
||||||
|
* @param instance Current Argon2 instance
|
||||||
|
* @return Zero if successful, -1 if memory failed to allocate. @context->state
|
||||||
|
* will be modified if successful.
|
||||||
|
*/
|
||||||
|
int initialize(argon2_instance_t *instance, argon2_context *context);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* XORing the last block of each lane, hashing it, making the tag. Deallocates
|
||||||
|
* the memory.
|
||||||
|
* @param context Pointer to current Argon2 context (use only the out parameters
|
||||||
|
* from it)
|
||||||
|
* @param instance Pointer to current instance of Argon2
|
||||||
|
* @pre instance->state must point to necessary amount of memory
|
||||||
|
* @pre context->out must point to outlen bytes of memory
|
||||||
|
* @pre if context->free_cbk is not NULL, it should point to a function that
|
||||||
|
* deallocates memory
|
||||||
|
*/
|
||||||
|
void finalize(const argon2_context *context, argon2_instance_t *instance);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function that fills the segment using previous segments also from other
|
||||||
|
* threads
|
||||||
|
* @param context current context
|
||||||
|
* @param instance Pointer to the current instance
|
||||||
|
* @param position Current position
|
||||||
|
* @pre all block pointers must be valid
|
||||||
|
*/
|
||||||
|
void fill_segment(const argon2_instance_t *instance,
|
||||||
|
argon2_position_t position);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function that fills the entire memory t_cost times based on the first two
|
||||||
|
* blocks in each lane
|
||||||
|
* @param instance Pointer to the current instance
|
||||||
|
* @return ARGON2_OK if successful, @context->state
|
||||||
|
*/
|
||||||
|
int fill_memory_blocks(argon2_instance_t *instance);
|
||||||
|
|
||||||
|
#endif
|
||||||
57
android/app/src/main/jni/encoding.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ENCODING_H
|
||||||
|
#define ENCODING_H
|
||||||
|
#include "argon2.h"
|
||||||
|
|
||||||
|
#define ARGON2_MAX_DECODED_LANES UINT32_C(255)
|
||||||
|
#define ARGON2_MIN_DECODED_SALT_LEN UINT32_C(8)
|
||||||
|
#define ARGON2_MIN_DECODED_OUT_LEN UINT32_C(12)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* encode an Argon2 hash string into the provided buffer. 'dst_len'
|
||||||
|
* contains the size, in characters, of the 'dst' buffer; if 'dst_len'
|
||||||
|
* is less than the number of required characters (including the
|
||||||
|
* terminating 0), then this function returns ARGON2_ENCODING_ERROR.
|
||||||
|
*
|
||||||
|
* on success, ARGON2_OK is returned.
|
||||||
|
*/
|
||||||
|
int encode_string(char *dst, size_t dst_len, argon2_context *ctx,
|
||||||
|
argon2_type type);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decodes an Argon2 hash string into the provided structure 'ctx'.
|
||||||
|
* The only fields that must be set prior to this call are ctx.saltlen and
|
||||||
|
* ctx.outlen (which must be the maximal salt and out length values that are
|
||||||
|
* allowed), ctx.salt and ctx.out (which must be buffers of the specified
|
||||||
|
* length), and ctx.pwd and ctx.pwdlen which must hold a valid password.
|
||||||
|
*
|
||||||
|
* Invalid input string causes an error. On success, the ctx is valid and all
|
||||||
|
* fields have been initialized.
|
||||||
|
*
|
||||||
|
* Returned value is ARGON2_OK on success, other ARGON2_ codes on error.
|
||||||
|
*/
|
||||||
|
int decode_string(argon2_context *ctx, const char *str, argon2_type type);
|
||||||
|
|
||||||
|
/* Returns the length of the encoded byte stream with length len */
|
||||||
|
size_t b64len(uint32_t len);
|
||||||
|
|
||||||
|
/* Returns the length of the encoded number num */
|
||||||
|
size_t numlen(uint32_t num);
|
||||||
|
|
||||||
|
#endif
|
||||||
452
android/app/src/main/jni/src/argon2.c
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "argon2.h"
|
||||||
|
#include "encoding.h"
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
const char *argon2_type2string(argon2_type type, int uppercase) {
|
||||||
|
switch (type) {
|
||||||
|
case Argon2_d:
|
||||||
|
return uppercase ? "Argon2d" : "argon2d";
|
||||||
|
case Argon2_i:
|
||||||
|
return uppercase ? "Argon2i" : "argon2i";
|
||||||
|
case Argon2_id:
|
||||||
|
return uppercase ? "Argon2id" : "argon2id";
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2_ctx(argon2_context *context, argon2_type type) {
|
||||||
|
/* 1. Validate all inputs */
|
||||||
|
int result = validate_inputs(context);
|
||||||
|
uint32_t memory_blocks, segment_length;
|
||||||
|
argon2_instance_t instance;
|
||||||
|
|
||||||
|
if (ARGON2_OK != result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Argon2_d != type && Argon2_i != type && Argon2_id != type) {
|
||||||
|
return ARGON2_INCORRECT_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Align memory size */
|
||||||
|
/* Minimum memory_blocks = 8L blocks, where L is the number of lanes */
|
||||||
|
memory_blocks = context->m_cost;
|
||||||
|
|
||||||
|
if (memory_blocks < 2 * ARGON2_SYNC_POINTS * context->lanes) {
|
||||||
|
memory_blocks = 2 * ARGON2_SYNC_POINTS * context->lanes;
|
||||||
|
}
|
||||||
|
|
||||||
|
segment_length = memory_blocks / (context->lanes * ARGON2_SYNC_POINTS);
|
||||||
|
/* Ensure that all segments have equal length */
|
||||||
|
memory_blocks = segment_length * (context->lanes * ARGON2_SYNC_POINTS);
|
||||||
|
|
||||||
|
instance.version = context->version;
|
||||||
|
instance.memory = NULL;
|
||||||
|
instance.passes = context->t_cost;
|
||||||
|
instance.memory_blocks = memory_blocks;
|
||||||
|
instance.segment_length = segment_length;
|
||||||
|
instance.lane_length = segment_length * ARGON2_SYNC_POINTS;
|
||||||
|
instance.lanes = context->lanes;
|
||||||
|
instance.threads = context->threads;
|
||||||
|
instance.type = type;
|
||||||
|
|
||||||
|
if (instance.threads > instance.lanes) {
|
||||||
|
instance.threads = instance.lanes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Initialization: Hashing inputs, allocating memory, filling first
|
||||||
|
* blocks
|
||||||
|
*/
|
||||||
|
result = initialize(&instance, context);
|
||||||
|
|
||||||
|
if (ARGON2_OK != result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Filling memory */
|
||||||
|
result = fill_memory_blocks(&instance);
|
||||||
|
|
||||||
|
if (ARGON2_OK != result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/* 5. Finalization */
|
||||||
|
finalize(context, &instance);
|
||||||
|
|
||||||
|
return ARGON2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2_hash(const uint32_t t_cost, const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt, const size_t saltlen,
|
||||||
|
void *hash, const size_t hashlen, char *encoded,
|
||||||
|
const size_t encodedlen, argon2_type type,
|
||||||
|
const uint32_t version){
|
||||||
|
|
||||||
|
argon2_context context;
|
||||||
|
int result;
|
||||||
|
uint8_t *out;
|
||||||
|
|
||||||
|
if (pwdlen > ARGON2_MAX_PWD_LENGTH) {
|
||||||
|
return ARGON2_PWD_TOO_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saltlen > ARGON2_MAX_SALT_LENGTH) {
|
||||||
|
return ARGON2_SALT_TOO_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashlen > ARGON2_MAX_OUTLEN) {
|
||||||
|
return ARGON2_OUTPUT_TOO_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashlen < ARGON2_MIN_OUTLEN) {
|
||||||
|
return ARGON2_OUTPUT_TOO_SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = malloc(hashlen);
|
||||||
|
if (!out) {
|
||||||
|
return ARGON2_MEMORY_ALLOCATION_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.out = (uint8_t *)out;
|
||||||
|
context.outlen = (uint32_t)hashlen;
|
||||||
|
context.pwd = CONST_CAST(uint8_t *)pwd;
|
||||||
|
context.pwdlen = (uint32_t)pwdlen;
|
||||||
|
context.salt = CONST_CAST(uint8_t *)salt;
|
||||||
|
context.saltlen = (uint32_t)saltlen;
|
||||||
|
context.secret = NULL;
|
||||||
|
context.secretlen = 0;
|
||||||
|
context.ad = NULL;
|
||||||
|
context.adlen = 0;
|
||||||
|
context.t_cost = t_cost;
|
||||||
|
context.m_cost = m_cost;
|
||||||
|
context.lanes = parallelism;
|
||||||
|
context.threads = parallelism;
|
||||||
|
context.allocate_cbk = NULL;
|
||||||
|
context.free_cbk = NULL;
|
||||||
|
context.flags = ARGON2_DEFAULT_FLAGS;
|
||||||
|
context.version = version;
|
||||||
|
|
||||||
|
result = argon2_ctx(&context, type);
|
||||||
|
|
||||||
|
if (result != ARGON2_OK) {
|
||||||
|
clear_internal_memory(out, hashlen);
|
||||||
|
free(out);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if raw hash requested, write it */
|
||||||
|
if (hash) {
|
||||||
|
memcpy(hash, out, hashlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if encoding requested, write it */
|
||||||
|
if (encoded && encodedlen) {
|
||||||
|
if (encode_string(encoded, encodedlen, &context, type) != ARGON2_OK) {
|
||||||
|
clear_internal_memory(out, hashlen); /* wipe buffers if error */
|
||||||
|
clear_internal_memory(encoded, encodedlen);
|
||||||
|
free(out);
|
||||||
|
return ARGON2_ENCODING_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clear_internal_memory(out, hashlen);
|
||||||
|
free(out);
|
||||||
|
|
||||||
|
return ARGON2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2i_hash_encoded(const uint32_t t_cost, const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt,
|
||||||
|
const size_t saltlen, const size_t hashlen,
|
||||||
|
char *encoded, const size_t encodedlen) {
|
||||||
|
|
||||||
|
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
|
||||||
|
NULL, hashlen, encoded, encodedlen, Argon2_i,
|
||||||
|
ARGON2_VERSION_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2i_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt,
|
||||||
|
const size_t saltlen, void *hash, const size_t hashlen) {
|
||||||
|
|
||||||
|
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
|
||||||
|
hash, hashlen, NULL, 0, Argon2_i, ARGON2_VERSION_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2d_hash_encoded(const uint32_t t_cost, const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt,
|
||||||
|
const size_t saltlen, const size_t hashlen,
|
||||||
|
char *encoded, const size_t encodedlen) {
|
||||||
|
|
||||||
|
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
|
||||||
|
NULL, hashlen, encoded, encodedlen, Argon2_d,
|
||||||
|
ARGON2_VERSION_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2d_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt,
|
||||||
|
const size_t saltlen, void *hash, const size_t hashlen) {
|
||||||
|
|
||||||
|
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
|
||||||
|
hash, hashlen, NULL, 0, Argon2_d, ARGON2_VERSION_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2id_hash_encoded(const uint32_t t_cost, const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt,
|
||||||
|
const size_t saltlen, const size_t hashlen,
|
||||||
|
char *encoded, const size_t encodedlen) {
|
||||||
|
|
||||||
|
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
|
||||||
|
NULL, hashlen, encoded, encodedlen, Argon2_id,
|
||||||
|
ARGON2_VERSION_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2id_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
|
||||||
|
const uint32_t parallelism, const void *pwd,
|
||||||
|
const size_t pwdlen, const void *salt,
|
||||||
|
const size_t saltlen, void *hash, const size_t hashlen) {
|
||||||
|
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
|
||||||
|
hash, hashlen, NULL, 0, Argon2_id,
|
||||||
|
ARGON2_VERSION_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int argon2_compare(const uint8_t *b1, const uint8_t *b2, size_t len) {
|
||||||
|
size_t i;
|
||||||
|
uint8_t d = 0U;
|
||||||
|
|
||||||
|
for (i = 0U; i < len; i++) {
|
||||||
|
d |= b1[i] ^ b2[i];
|
||||||
|
}
|
||||||
|
return (int)((1 & ((d - 1) >> 8)) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2_verify(const char *encoded, const void *pwd, const size_t pwdlen,
|
||||||
|
argon2_type type) {
|
||||||
|
|
||||||
|
argon2_context ctx;
|
||||||
|
uint8_t *desired_result = NULL;
|
||||||
|
|
||||||
|
int ret = ARGON2_OK;
|
||||||
|
|
||||||
|
size_t encoded_len;
|
||||||
|
uint32_t max_field_len;
|
||||||
|
|
||||||
|
if (pwdlen > ARGON2_MAX_PWD_LENGTH) {
|
||||||
|
return ARGON2_PWD_TOO_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoded == NULL) {
|
||||||
|
return ARGON2_DECODING_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded_len = strlen(encoded);
|
||||||
|
if (encoded_len > UINT32_MAX) {
|
||||||
|
return ARGON2_DECODING_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No field can be longer than the encoded length */
|
||||||
|
max_field_len = (uint32_t)encoded_len;
|
||||||
|
|
||||||
|
ctx.saltlen = max_field_len;
|
||||||
|
ctx.outlen = max_field_len;
|
||||||
|
|
||||||
|
ctx.salt = malloc(ctx.saltlen);
|
||||||
|
ctx.out = malloc(ctx.outlen);
|
||||||
|
if (!ctx.salt || !ctx.out) {
|
||||||
|
ret = ARGON2_MEMORY_ALLOCATION_ERROR;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.pwd = (uint8_t *)pwd;
|
||||||
|
ctx.pwdlen = (uint32_t)pwdlen;
|
||||||
|
|
||||||
|
ret = decode_string(&ctx, encoded, type);
|
||||||
|
if (ret != ARGON2_OK) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set aside the desired result, and get a new buffer. */
|
||||||
|
desired_result = ctx.out;
|
||||||
|
ctx.out = malloc(ctx.outlen);
|
||||||
|
if (!ctx.out) {
|
||||||
|
ret = ARGON2_MEMORY_ALLOCATION_ERROR;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = argon2_verify_ctx(&ctx, (char *)desired_result, type);
|
||||||
|
if (ret != ARGON2_OK) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail:
|
||||||
|
free(ctx.salt);
|
||||||
|
free(ctx.out);
|
||||||
|
free(desired_result);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2i_verify(const char *encoded, const void *pwd, const size_t pwdlen) {
|
||||||
|
|
||||||
|
return argon2_verify(encoded, pwd, pwdlen, Argon2_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2d_verify(const char *encoded, const void *pwd, const size_t pwdlen) {
|
||||||
|
|
||||||
|
return argon2_verify(encoded, pwd, pwdlen, Argon2_d);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2id_verify(const char *encoded, const void *pwd, const size_t pwdlen) {
|
||||||
|
|
||||||
|
return argon2_verify(encoded, pwd, pwdlen, Argon2_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2d_ctx(argon2_context *context) {
|
||||||
|
return argon2_ctx(context, Argon2_d);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2i_ctx(argon2_context *context) {
|
||||||
|
return argon2_ctx(context, Argon2_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2id_ctx(argon2_context *context) {
|
||||||
|
return argon2_ctx(context, Argon2_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2_verify_ctx(argon2_context *context, const char *hash,
|
||||||
|
argon2_type type) {
|
||||||
|
int ret = argon2_ctx(context, type);
|
||||||
|
if (ret != ARGON2_OK) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argon2_compare((uint8_t *)hash, context->out, context->outlen)) {
|
||||||
|
return ARGON2_VERIFY_MISMATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ARGON2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2d_verify_ctx(argon2_context *context, const char *hash) {
|
||||||
|
return argon2_verify_ctx(context, hash, Argon2_d);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2i_verify_ctx(argon2_context *context, const char *hash) {
|
||||||
|
return argon2_verify_ctx(context, hash, Argon2_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2id_verify_ctx(argon2_context *context, const char *hash) {
|
||||||
|
return argon2_verify_ctx(context, hash, Argon2_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *argon2_error_message(int error_code) {
|
||||||
|
switch (error_code) {
|
||||||
|
case ARGON2_OK:
|
||||||
|
return "OK";
|
||||||
|
case ARGON2_OUTPUT_PTR_NULL:
|
||||||
|
return "Output pointer is NULL";
|
||||||
|
case ARGON2_OUTPUT_TOO_SHORT:
|
||||||
|
return "Output is too short";
|
||||||
|
case ARGON2_OUTPUT_TOO_LONG:
|
||||||
|
return "Output is too long";
|
||||||
|
case ARGON2_PWD_TOO_SHORT:
|
||||||
|
return "Password is too short";
|
||||||
|
case ARGON2_PWD_TOO_LONG:
|
||||||
|
return "Password is too long";
|
||||||
|
case ARGON2_SALT_TOO_SHORT:
|
||||||
|
return "Salt is too short";
|
||||||
|
case ARGON2_SALT_TOO_LONG:
|
||||||
|
return "Salt is too long";
|
||||||
|
case ARGON2_AD_TOO_SHORT:
|
||||||
|
return "Associated data is too short";
|
||||||
|
case ARGON2_AD_TOO_LONG:
|
||||||
|
return "Associated data is too long";
|
||||||
|
case ARGON2_SECRET_TOO_SHORT:
|
||||||
|
return "Secret is too short";
|
||||||
|
case ARGON2_SECRET_TOO_LONG:
|
||||||
|
return "Secret is too long";
|
||||||
|
case ARGON2_TIME_TOO_SMALL:
|
||||||
|
return "Time cost is too small";
|
||||||
|
case ARGON2_TIME_TOO_LARGE:
|
||||||
|
return "Time cost is too large";
|
||||||
|
case ARGON2_MEMORY_TOO_LITTLE:
|
||||||
|
return "Memory cost is too small";
|
||||||
|
case ARGON2_MEMORY_TOO_MUCH:
|
||||||
|
return "Memory cost is too large";
|
||||||
|
case ARGON2_LANES_TOO_FEW:
|
||||||
|
return "Too few lanes";
|
||||||
|
case ARGON2_LANES_TOO_MANY:
|
||||||
|
return "Too many lanes";
|
||||||
|
case ARGON2_PWD_PTR_MISMATCH:
|
||||||
|
return "Password pointer is NULL, but password length is not 0";
|
||||||
|
case ARGON2_SALT_PTR_MISMATCH:
|
||||||
|
return "Salt pointer is NULL, but salt length is not 0";
|
||||||
|
case ARGON2_SECRET_PTR_MISMATCH:
|
||||||
|
return "Secret pointer is NULL, but secret length is not 0";
|
||||||
|
case ARGON2_AD_PTR_MISMATCH:
|
||||||
|
return "Associated data pointer is NULL, but ad length is not 0";
|
||||||
|
case ARGON2_MEMORY_ALLOCATION_ERROR:
|
||||||
|
return "Memory allocation error";
|
||||||
|
case ARGON2_FREE_MEMORY_CBK_NULL:
|
||||||
|
return "The free memory callback is NULL";
|
||||||
|
case ARGON2_ALLOCATE_MEMORY_CBK_NULL:
|
||||||
|
return "The allocate memory callback is NULL";
|
||||||
|
case ARGON2_INCORRECT_PARAMETER:
|
||||||
|
return "Argon2_Context context is NULL";
|
||||||
|
case ARGON2_INCORRECT_TYPE:
|
||||||
|
return "There is no such version of Argon2";
|
||||||
|
case ARGON2_OUT_PTR_MISMATCH:
|
||||||
|
return "Output pointer mismatch";
|
||||||
|
case ARGON2_THREADS_TOO_FEW:
|
||||||
|
return "Not enough threads";
|
||||||
|
case ARGON2_THREADS_TOO_MANY:
|
||||||
|
return "Too many threads";
|
||||||
|
case ARGON2_MISSING_ARGS:
|
||||||
|
return "Missing arguments";
|
||||||
|
case ARGON2_ENCODING_FAIL:
|
||||||
|
return "Encoding failed";
|
||||||
|
case ARGON2_DECODING_FAIL:
|
||||||
|
return "Decoding failed";
|
||||||
|
case ARGON2_THREAD_FAIL:
|
||||||
|
return "Threading failure";
|
||||||
|
case ARGON2_DECODING_LENGTH_FAIL:
|
||||||
|
return "Some of encoded parameters are too long or too short";
|
||||||
|
case ARGON2_VERIFY_MISMATCH:
|
||||||
|
return "The password does not match the supplied hash";
|
||||||
|
default:
|
||||||
|
return "Unknown error code";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost, uint32_t parallelism,
|
||||||
|
uint32_t saltlen, uint32_t hashlen, argon2_type type) {
|
||||||
|
return strlen("$$v=$m=,t=,p=$$") + strlen(argon2_type2string(type, 0)) +
|
||||||
|
numlen(t_cost) + numlen(m_cost) + numlen(parallelism) +
|
||||||
|
b64len(saltlen) + b64len(hashlen) + numlen(ARGON2_VERSION_NUMBER) + 1;
|
||||||
|
}
|
||||||
156
android/app/src/main/jni/src/blake2/blake2-impl.h
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PORTABLE_BLAKE2_IMPL_H
|
||||||
|
#define PORTABLE_BLAKE2_IMPL_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define BLAKE2_INLINE __inline
|
||||||
|
#elif defined(__GNUC__) || defined(__clang__)
|
||||||
|
#define BLAKE2_INLINE __inline__
|
||||||
|
#else
|
||||||
|
#define BLAKE2_INLINE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Argon2 Team - Begin Code */
|
||||||
|
/*
|
||||||
|
Not an exhaustive list, but should cover the majority of modern platforms
|
||||||
|
Additionally, the code will always be correct---this is only a performance
|
||||||
|
tweak.
|
||||||
|
*/
|
||||||
|
#if (defined(__BYTE_ORDER__) && \
|
||||||
|
(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) || \
|
||||||
|
defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__MIPSEL__) || \
|
||||||
|
defined(__AARCH64EL__) || defined(__amd64__) || defined(__i386__) || \
|
||||||
|
defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) || \
|
||||||
|
defined(_M_ARM)
|
||||||
|
#define NATIVE_LITTLE_ENDIAN
|
||||||
|
#endif
|
||||||
|
/* Argon2 Team - End Code */
|
||||||
|
|
||||||
|
static BLAKE2_INLINE uint32_t load32(const void *src) {
|
||||||
|
#if defined(NATIVE_LITTLE_ENDIAN)
|
||||||
|
uint32_t w;
|
||||||
|
memcpy(&w, src, sizeof w);
|
||||||
|
return w;
|
||||||
|
#else
|
||||||
|
const uint8_t *p = (const uint8_t *)src;
|
||||||
|
uint32_t w = *p++;
|
||||||
|
w |= (uint32_t)(*p++) << 8;
|
||||||
|
w |= (uint32_t)(*p++) << 16;
|
||||||
|
w |= (uint32_t)(*p++) << 24;
|
||||||
|
return w;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE uint64_t load64(const void *src) {
|
||||||
|
#if defined(NATIVE_LITTLE_ENDIAN)
|
||||||
|
uint64_t w;
|
||||||
|
memcpy(&w, src, sizeof w);
|
||||||
|
return w;
|
||||||
|
#else
|
||||||
|
const uint8_t *p = (const uint8_t *)src;
|
||||||
|
uint64_t w = *p++;
|
||||||
|
w |= (uint64_t)(*p++) << 8;
|
||||||
|
w |= (uint64_t)(*p++) << 16;
|
||||||
|
w |= (uint64_t)(*p++) << 24;
|
||||||
|
w |= (uint64_t)(*p++) << 32;
|
||||||
|
w |= (uint64_t)(*p++) << 40;
|
||||||
|
w |= (uint64_t)(*p++) << 48;
|
||||||
|
w |= (uint64_t)(*p++) << 56;
|
||||||
|
return w;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void store32(void *dst, uint32_t w) {
|
||||||
|
#if defined(NATIVE_LITTLE_ENDIAN)
|
||||||
|
memcpy(dst, &w, sizeof w);
|
||||||
|
#else
|
||||||
|
uint8_t *p = (uint8_t *)dst;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void store64(void *dst, uint64_t w) {
|
||||||
|
#if defined(NATIVE_LITTLE_ENDIAN)
|
||||||
|
memcpy(dst, &w, sizeof w);
|
||||||
|
#else
|
||||||
|
uint8_t *p = (uint8_t *)dst;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE uint64_t load48(const void *src) {
|
||||||
|
const uint8_t *p = (const uint8_t *)src;
|
||||||
|
uint64_t w = *p++;
|
||||||
|
w |= (uint64_t)(*p++) << 8;
|
||||||
|
w |= (uint64_t)(*p++) << 16;
|
||||||
|
w |= (uint64_t)(*p++) << 24;
|
||||||
|
w |= (uint64_t)(*p++) << 32;
|
||||||
|
w |= (uint64_t)(*p++) << 40;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void store48(void *dst, uint64_t w) {
|
||||||
|
uint8_t *p = (uint8_t *)dst;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
w >>= 8;
|
||||||
|
*p++ = (uint8_t)w;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE uint32_t rotr32(const uint32_t w, const unsigned c) {
|
||||||
|
return (w >> c) | (w << (32 - c));
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE uint64_t rotr64(const uint64_t w, const unsigned c) {
|
||||||
|
return (w >> c) | (w << (64 - c));
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_internal_memory(void *v, size_t n);
|
||||||
|
|
||||||
|
#endif
|
||||||
89
android/app/src/main/jni/src/blake2/blake2.h
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PORTABLE_BLAKE2_H
|
||||||
|
#define PORTABLE_BLAKE2_H
|
||||||
|
|
||||||
|
#include <argon2.h>
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum blake2b_constant {
|
||||||
|
BLAKE2B_BLOCKBYTES = 128,
|
||||||
|
BLAKE2B_OUTBYTES = 64,
|
||||||
|
BLAKE2B_KEYBYTES = 64,
|
||||||
|
BLAKE2B_SALTBYTES = 16,
|
||||||
|
BLAKE2B_PERSONALBYTES = 16
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct __blake2b_param {
|
||||||
|
uint8_t digest_length; /* 1 */
|
||||||
|
uint8_t key_length; /* 2 */
|
||||||
|
uint8_t fanout; /* 3 */
|
||||||
|
uint8_t depth; /* 4 */
|
||||||
|
uint32_t leaf_length; /* 8 */
|
||||||
|
uint64_t node_offset; /* 16 */
|
||||||
|
uint8_t node_depth; /* 17 */
|
||||||
|
uint8_t inner_length; /* 18 */
|
||||||
|
uint8_t reserved[14]; /* 32 */
|
||||||
|
uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */
|
||||||
|
uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */
|
||||||
|
} blake2b_param;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
typedef struct __blake2b_state {
|
||||||
|
uint64_t h[8];
|
||||||
|
uint64_t t[2];
|
||||||
|
uint64_t f[2];
|
||||||
|
uint8_t buf[BLAKE2B_BLOCKBYTES];
|
||||||
|
unsigned buflen;
|
||||||
|
unsigned outlen;
|
||||||
|
uint8_t last_node;
|
||||||
|
} blake2b_state;
|
||||||
|
|
||||||
|
/* Ensure param structs have not been wrongly padded */
|
||||||
|
/* Poor man's static_assert */
|
||||||
|
enum {
|
||||||
|
blake2_size_check_0 = 1 / !!(CHAR_BIT == 8),
|
||||||
|
blake2_size_check_2 =
|
||||||
|
1 / !!(sizeof(blake2b_param) == sizeof(uint64_t) * CHAR_BIT)
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Streaming API */
|
||||||
|
ARGON2_LOCAL int blake2b_init(blake2b_state *S, size_t outlen);
|
||||||
|
ARGON2_LOCAL int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key,
|
||||||
|
size_t keylen);
|
||||||
|
ARGON2_LOCAL int blake2b_init_param(blake2b_state *S, const blake2b_param *P);
|
||||||
|
ARGON2_LOCAL int blake2b_update(blake2b_state *S, const void *in, size_t inlen);
|
||||||
|
ARGON2_LOCAL int blake2b_final(blake2b_state *S, void *out, size_t outlen);
|
||||||
|
|
||||||
|
/* Simple API */
|
||||||
|
ARGON2_LOCAL int blake2b(void *out, size_t outlen, const void *in, size_t inlen,
|
||||||
|
const void *key, size_t keylen);
|
||||||
|
|
||||||
|
/* Argon2 Team - Begin Code */
|
||||||
|
ARGON2_LOCAL int blake2b_long(void *out, size_t outlen, const void *in, size_t inlen);
|
||||||
|
/* Argon2 Team - End Code */
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
390
android/app/src/main/jni/src/blake2/blake2b.c
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "blake2.h"
|
||||||
|
#include "blake2-impl.h"
|
||||||
|
|
||||||
|
static const uint64_t blake2b_IV[8] = {
|
||||||
|
UINT64_C(0x6a09e667f3bcc908), UINT64_C(0xbb67ae8584caa73b),
|
||||||
|
UINT64_C(0x3c6ef372fe94f82b), UINT64_C(0xa54ff53a5f1d36f1),
|
||||||
|
UINT64_C(0x510e527fade682d1), UINT64_C(0x9b05688c2b3e6c1f),
|
||||||
|
UINT64_C(0x1f83d9abfb41bd6b), UINT64_C(0x5be0cd19137e2179)};
|
||||||
|
|
||||||
|
static const unsigned int blake2b_sigma[12][16] = {
|
||||||
|
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||||
|
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
|
||||||
|
{11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
|
||||||
|
{7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
|
||||||
|
{9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
|
||||||
|
{2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
|
||||||
|
{12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
|
||||||
|
{13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
|
||||||
|
{6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
|
||||||
|
{10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0},
|
||||||
|
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||||
|
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
|
||||||
|
};
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void blake2b_set_lastnode(blake2b_state *S) {
|
||||||
|
S->f[1] = (uint64_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void blake2b_set_lastblock(blake2b_state *S) {
|
||||||
|
if (S->last_node) {
|
||||||
|
blake2b_set_lastnode(S);
|
||||||
|
}
|
||||||
|
S->f[0] = (uint64_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void blake2b_increment_counter(blake2b_state *S,
|
||||||
|
uint64_t inc) {
|
||||||
|
S->t[0] += inc;
|
||||||
|
S->t[1] += (S->t[0] < inc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void blake2b_invalidate_state(blake2b_state *S) {
|
||||||
|
clear_internal_memory(S, sizeof(*S)); /* wipe */
|
||||||
|
blake2b_set_lastblock(S); /* invalidate for further use */
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void blake2b_init0(blake2b_state *S) {
|
||||||
|
memset(S, 0, sizeof(*S));
|
||||||
|
memcpy(S->h, blake2b_IV, sizeof(S->h));
|
||||||
|
}
|
||||||
|
|
||||||
|
int blake2b_init_param(blake2b_state *S, const blake2b_param *P) {
|
||||||
|
const unsigned char *p = (const unsigned char *)P;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
if (NULL == P || NULL == S) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
blake2b_init0(S);
|
||||||
|
/* IV XOR Parameter Block */
|
||||||
|
for (i = 0; i < 8; ++i) {
|
||||||
|
S->h[i] ^= load64(&p[i * sizeof(S->h[i])]);
|
||||||
|
}
|
||||||
|
S->outlen = P->digest_length;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sequential blake2b initialization */
|
||||||
|
int blake2b_init(blake2b_state *S, size_t outlen) {
|
||||||
|
blake2b_param P;
|
||||||
|
|
||||||
|
if (S == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) {
|
||||||
|
blake2b_invalidate_state(S);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup Parameter Block for unkeyed BLAKE2 */
|
||||||
|
P.digest_length = (uint8_t)outlen;
|
||||||
|
P.key_length = 0;
|
||||||
|
P.fanout = 1;
|
||||||
|
P.depth = 1;
|
||||||
|
P.leaf_length = 0;
|
||||||
|
P.node_offset = 0;
|
||||||
|
P.node_depth = 0;
|
||||||
|
P.inner_length = 0;
|
||||||
|
memset(P.reserved, 0, sizeof(P.reserved));
|
||||||
|
memset(P.salt, 0, sizeof(P.salt));
|
||||||
|
memset(P.personal, 0, sizeof(P.personal));
|
||||||
|
|
||||||
|
return blake2b_init_param(S, &P);
|
||||||
|
}
|
||||||
|
|
||||||
|
int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key,
|
||||||
|
size_t keylen) {
|
||||||
|
blake2b_param P;
|
||||||
|
|
||||||
|
if (S == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) {
|
||||||
|
blake2b_invalidate_state(S);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((key == 0) || (keylen == 0) || (keylen > BLAKE2B_KEYBYTES)) {
|
||||||
|
blake2b_invalidate_state(S);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup Parameter Block for keyed BLAKE2 */
|
||||||
|
P.digest_length = (uint8_t)outlen;
|
||||||
|
P.key_length = (uint8_t)keylen;
|
||||||
|
P.fanout = 1;
|
||||||
|
P.depth = 1;
|
||||||
|
P.leaf_length = 0;
|
||||||
|
P.node_offset = 0;
|
||||||
|
P.node_depth = 0;
|
||||||
|
P.inner_length = 0;
|
||||||
|
memset(P.reserved, 0, sizeof(P.reserved));
|
||||||
|
memset(P.salt, 0, sizeof(P.salt));
|
||||||
|
memset(P.personal, 0, sizeof(P.personal));
|
||||||
|
|
||||||
|
if (blake2b_init_param(S, &P) < 0) {
|
||||||
|
blake2b_invalidate_state(S);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
uint8_t block[BLAKE2B_BLOCKBYTES];
|
||||||
|
memset(block, 0, BLAKE2B_BLOCKBYTES);
|
||||||
|
memcpy(block, key, keylen);
|
||||||
|
blake2b_update(S, block, BLAKE2B_BLOCKBYTES);
|
||||||
|
/* Burn the key from stack */
|
||||||
|
clear_internal_memory(block, BLAKE2B_BLOCKBYTES);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void blake2b_compress(blake2b_state *S, const uint8_t *block) {
|
||||||
|
uint64_t m[16];
|
||||||
|
uint64_t v[16];
|
||||||
|
unsigned int i, r;
|
||||||
|
|
||||||
|
for (i = 0; i < 16; ++i) {
|
||||||
|
m[i] = load64(block + i * sizeof(m[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 8; ++i) {
|
||||||
|
v[i] = S->h[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
v[8] = blake2b_IV[0];
|
||||||
|
v[9] = blake2b_IV[1];
|
||||||
|
v[10] = blake2b_IV[2];
|
||||||
|
v[11] = blake2b_IV[3];
|
||||||
|
v[12] = blake2b_IV[4] ^ S->t[0];
|
||||||
|
v[13] = blake2b_IV[5] ^ S->t[1];
|
||||||
|
v[14] = blake2b_IV[6] ^ S->f[0];
|
||||||
|
v[15] = blake2b_IV[7] ^ S->f[1];
|
||||||
|
|
||||||
|
#define G(r, i, a, b, c, d) \
|
||||||
|
do { \
|
||||||
|
a = a + b + m[blake2b_sigma[r][2 * i + 0]]; \
|
||||||
|
d = rotr64(d ^ a, 32); \
|
||||||
|
c = c + d; \
|
||||||
|
b = rotr64(b ^ c, 24); \
|
||||||
|
a = a + b + m[blake2b_sigma[r][2 * i + 1]]; \
|
||||||
|
d = rotr64(d ^ a, 16); \
|
||||||
|
c = c + d; \
|
||||||
|
b = rotr64(b ^ c, 63); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define ROUND(r) \
|
||||||
|
do { \
|
||||||
|
G(r, 0, v[0], v[4], v[8], v[12]); \
|
||||||
|
G(r, 1, v[1], v[5], v[9], v[13]); \
|
||||||
|
G(r, 2, v[2], v[6], v[10], v[14]); \
|
||||||
|
G(r, 3, v[3], v[7], v[11], v[15]); \
|
||||||
|
G(r, 4, v[0], v[5], v[10], v[15]); \
|
||||||
|
G(r, 5, v[1], v[6], v[11], v[12]); \
|
||||||
|
G(r, 6, v[2], v[7], v[8], v[13]); \
|
||||||
|
G(r, 7, v[3], v[4], v[9], v[14]); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
for (r = 0; r < 12; ++r) {
|
||||||
|
ROUND(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 8; ++i) {
|
||||||
|
S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef G
|
||||||
|
#undef ROUND
|
||||||
|
}
|
||||||
|
|
||||||
|
int blake2b_update(blake2b_state *S, const void *in, size_t inlen) {
|
||||||
|
const uint8_t *pin = (const uint8_t *)in;
|
||||||
|
|
||||||
|
if (inlen == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sanity check */
|
||||||
|
if (S == NULL || in == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Is this a reused state? */
|
||||||
|
if (S->f[0] != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (S->buflen + inlen > BLAKE2B_BLOCKBYTES) {
|
||||||
|
/* Complete current block */
|
||||||
|
size_t left = S->buflen;
|
||||||
|
size_t fill = BLAKE2B_BLOCKBYTES - left;
|
||||||
|
memcpy(&S->buf[left], pin, fill);
|
||||||
|
blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
|
||||||
|
blake2b_compress(S, S->buf);
|
||||||
|
S->buflen = 0;
|
||||||
|
inlen -= fill;
|
||||||
|
pin += fill;
|
||||||
|
/* Avoid buffer copies when possible */
|
||||||
|
while (inlen > BLAKE2B_BLOCKBYTES) {
|
||||||
|
blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
|
||||||
|
blake2b_compress(S, pin);
|
||||||
|
inlen -= BLAKE2B_BLOCKBYTES;
|
||||||
|
pin += BLAKE2B_BLOCKBYTES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(&S->buf[S->buflen], pin, inlen);
|
||||||
|
S->buflen += (unsigned int)inlen;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int blake2b_final(blake2b_state *S, void *out, size_t outlen) {
|
||||||
|
uint8_t buffer[BLAKE2B_OUTBYTES] = {0};
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
/* Sanity checks */
|
||||||
|
if (S == NULL || out == NULL || outlen < S->outlen) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Is this a reused state? */
|
||||||
|
if (S->f[0] != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
blake2b_increment_counter(S, S->buflen);
|
||||||
|
blake2b_set_lastblock(S);
|
||||||
|
memset(&S->buf[S->buflen], 0, BLAKE2B_BLOCKBYTES - S->buflen); /* Padding */
|
||||||
|
blake2b_compress(S, S->buf);
|
||||||
|
|
||||||
|
for (i = 0; i < 8; ++i) { /* Output full hash to temp buffer */
|
||||||
|
store64(buffer + sizeof(S->h[i]) * i, S->h[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(out, buffer, S->outlen);
|
||||||
|
clear_internal_memory(buffer, sizeof(buffer));
|
||||||
|
clear_internal_memory(S->buf, sizeof(S->buf));
|
||||||
|
clear_internal_memory(S->h, sizeof(S->h));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int blake2b(void *out, size_t outlen, const void *in, size_t inlen,
|
||||||
|
const void *key, size_t keylen) {
|
||||||
|
blake2b_state S;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
/* Verify parameters */
|
||||||
|
if (NULL == in && inlen > 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NULL == out || outlen == 0 || outlen > BLAKE2B_OUTBYTES) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((NULL == key && keylen > 0) || keylen > BLAKE2B_KEYBYTES) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keylen > 0) {
|
||||||
|
if (blake2b_init_key(&S, outlen, key, keylen) < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (blake2b_init(&S, outlen) < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blake2b_update(&S, in, inlen) < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
ret = blake2b_final(&S, out, outlen);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
clear_internal_memory(&S, sizeof(S));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Argon2 Team - Begin Code */
|
||||||
|
int blake2b_long(void *pout, size_t outlen, const void *in, size_t inlen) {
|
||||||
|
uint8_t *out = (uint8_t *)pout;
|
||||||
|
blake2b_state blake_state;
|
||||||
|
uint8_t outlen_bytes[sizeof(uint32_t)] = {0};
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
if (outlen > UINT32_MAX) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure little-endian byte order! */
|
||||||
|
store32(outlen_bytes, (uint32_t)outlen);
|
||||||
|
|
||||||
|
#define TRY(statement) \
|
||||||
|
do { \
|
||||||
|
ret = statement; \
|
||||||
|
if (ret < 0) { \
|
||||||
|
goto fail; \
|
||||||
|
} \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
if (outlen <= BLAKE2B_OUTBYTES) {
|
||||||
|
TRY(blake2b_init(&blake_state, outlen));
|
||||||
|
TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
|
||||||
|
TRY(blake2b_update(&blake_state, in, inlen));
|
||||||
|
TRY(blake2b_final(&blake_state, out, outlen));
|
||||||
|
} else {
|
||||||
|
uint32_t toproduce;
|
||||||
|
uint8_t out_buffer[BLAKE2B_OUTBYTES];
|
||||||
|
uint8_t in_buffer[BLAKE2B_OUTBYTES];
|
||||||
|
TRY(blake2b_init(&blake_state, BLAKE2B_OUTBYTES));
|
||||||
|
TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
|
||||||
|
TRY(blake2b_update(&blake_state, in, inlen));
|
||||||
|
TRY(blake2b_final(&blake_state, out_buffer, BLAKE2B_OUTBYTES));
|
||||||
|
memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2);
|
||||||
|
out += BLAKE2B_OUTBYTES / 2;
|
||||||
|
toproduce = (uint32_t)outlen - BLAKE2B_OUTBYTES / 2;
|
||||||
|
|
||||||
|
while (toproduce > BLAKE2B_OUTBYTES) {
|
||||||
|
memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES);
|
||||||
|
TRY(blake2b(out_buffer, BLAKE2B_OUTBYTES, in_buffer,
|
||||||
|
BLAKE2B_OUTBYTES, NULL, 0));
|
||||||
|
memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2);
|
||||||
|
out += BLAKE2B_OUTBYTES / 2;
|
||||||
|
toproduce -= BLAKE2B_OUTBYTES / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES);
|
||||||
|
TRY(blake2b(out_buffer, toproduce, in_buffer, BLAKE2B_OUTBYTES, NULL,
|
||||||
|
0));
|
||||||
|
memcpy(out, out_buffer, toproduce);
|
||||||
|
}
|
||||||
|
fail:
|
||||||
|
clear_internal_memory(&blake_state, sizeof(blake_state));
|
||||||
|
return ret;
|
||||||
|
#undef TRY
|
||||||
|
}
|
||||||
|
/* Argon2 Team - End Code */
|
||||||
471
android/app/src/main/jni/src/blake2/blamka-round-opt.h
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BLAKE_ROUND_MKA_OPT_H
|
||||||
|
#define BLAKE_ROUND_MKA_OPT_H
|
||||||
|
|
||||||
|
#include "blake2-impl.h"
|
||||||
|
|
||||||
|
#include <emmintrin.h>
|
||||||
|
#if defined(__SSSE3__)
|
||||||
|
#include <tmmintrin.h> /* for _mm_shuffle_epi8 and _mm_alignr_epi8 */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__XOP__) && (defined(__GNUC__) || defined(__clang__))
|
||||||
|
#include <x86intrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(__AVX512F__)
|
||||||
|
#if !defined(__AVX2__)
|
||||||
|
#if !defined(__XOP__)
|
||||||
|
#if defined(__SSSE3__)
|
||||||
|
#define r16 \
|
||||||
|
(_mm_setr_epi8(2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9))
|
||||||
|
#define r24 \
|
||||||
|
(_mm_setr_epi8(3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10))
|
||||||
|
#define _mm_roti_epi64(x, c) \
|
||||||
|
(-(c) == 32) \
|
||||||
|
? _mm_shuffle_epi32((x), _MM_SHUFFLE(2, 3, 0, 1)) \
|
||||||
|
: (-(c) == 24) \
|
||||||
|
? _mm_shuffle_epi8((x), r24) \
|
||||||
|
: (-(c) == 16) \
|
||||||
|
? _mm_shuffle_epi8((x), r16) \
|
||||||
|
: (-(c) == 63) \
|
||||||
|
? _mm_xor_si128(_mm_srli_epi64((x), -(c)), \
|
||||||
|
_mm_add_epi64((x), (x))) \
|
||||||
|
: _mm_xor_si128(_mm_srli_epi64((x), -(c)), \
|
||||||
|
_mm_slli_epi64((x), 64 - (-(c))))
|
||||||
|
#else /* defined(__SSE2__) */
|
||||||
|
#define _mm_roti_epi64(r, c) \
|
||||||
|
_mm_xor_si128(_mm_srli_epi64((r), -(c)), _mm_slli_epi64((r), 64 - (-(c))))
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static BLAKE2_INLINE __m128i fBlaMka(__m128i x, __m128i y) {
|
||||||
|
const __m128i z = _mm_mul_epu32(x, y);
|
||||||
|
return _mm_add_epi64(_mm_add_epi64(x, y), _mm_add_epi64(z, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
#define G1(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
A0 = fBlaMka(A0, B0); \
|
||||||
|
A1 = fBlaMka(A1, B1); \
|
||||||
|
\
|
||||||
|
D0 = _mm_xor_si128(D0, A0); \
|
||||||
|
D1 = _mm_xor_si128(D1, A1); \
|
||||||
|
\
|
||||||
|
D0 = _mm_roti_epi64(D0, -32); \
|
||||||
|
D1 = _mm_roti_epi64(D1, -32); \
|
||||||
|
\
|
||||||
|
C0 = fBlaMka(C0, D0); \
|
||||||
|
C1 = fBlaMka(C1, D1); \
|
||||||
|
\
|
||||||
|
B0 = _mm_xor_si128(B0, C0); \
|
||||||
|
B1 = _mm_xor_si128(B1, C1); \
|
||||||
|
\
|
||||||
|
B0 = _mm_roti_epi64(B0, -24); \
|
||||||
|
B1 = _mm_roti_epi64(B1, -24); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define G2(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
A0 = fBlaMka(A0, B0); \
|
||||||
|
A1 = fBlaMka(A1, B1); \
|
||||||
|
\
|
||||||
|
D0 = _mm_xor_si128(D0, A0); \
|
||||||
|
D1 = _mm_xor_si128(D1, A1); \
|
||||||
|
\
|
||||||
|
D0 = _mm_roti_epi64(D0, -16); \
|
||||||
|
D1 = _mm_roti_epi64(D1, -16); \
|
||||||
|
\
|
||||||
|
C0 = fBlaMka(C0, D0); \
|
||||||
|
C1 = fBlaMka(C1, D1); \
|
||||||
|
\
|
||||||
|
B0 = _mm_xor_si128(B0, C0); \
|
||||||
|
B1 = _mm_xor_si128(B1, C1); \
|
||||||
|
\
|
||||||
|
B0 = _mm_roti_epi64(B0, -63); \
|
||||||
|
B1 = _mm_roti_epi64(B1, -63); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#if defined(__SSSE3__)
|
||||||
|
#define DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
__m128i t0 = _mm_alignr_epi8(B1, B0, 8); \
|
||||||
|
__m128i t1 = _mm_alignr_epi8(B0, B1, 8); \
|
||||||
|
B0 = t0; \
|
||||||
|
B1 = t1; \
|
||||||
|
\
|
||||||
|
t0 = C0; \
|
||||||
|
C0 = C1; \
|
||||||
|
C1 = t0; \
|
||||||
|
\
|
||||||
|
t0 = _mm_alignr_epi8(D1, D0, 8); \
|
||||||
|
t1 = _mm_alignr_epi8(D0, D1, 8); \
|
||||||
|
D0 = t1; \
|
||||||
|
D1 = t0; \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
__m128i t0 = _mm_alignr_epi8(B0, B1, 8); \
|
||||||
|
__m128i t1 = _mm_alignr_epi8(B1, B0, 8); \
|
||||||
|
B0 = t0; \
|
||||||
|
B1 = t1; \
|
||||||
|
\
|
||||||
|
t0 = C0; \
|
||||||
|
C0 = C1; \
|
||||||
|
C1 = t0; \
|
||||||
|
\
|
||||||
|
t0 = _mm_alignr_epi8(D0, D1, 8); \
|
||||||
|
t1 = _mm_alignr_epi8(D1, D0, 8); \
|
||||||
|
D0 = t1; \
|
||||||
|
D1 = t0; \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
#else /* SSE2 */
|
||||||
|
#define DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
__m128i t0 = D0; \
|
||||||
|
__m128i t1 = B0; \
|
||||||
|
D0 = C0; \
|
||||||
|
C0 = C1; \
|
||||||
|
C1 = D0; \
|
||||||
|
D0 = _mm_unpackhi_epi64(D1, _mm_unpacklo_epi64(t0, t0)); \
|
||||||
|
D1 = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(D1, D1)); \
|
||||||
|
B0 = _mm_unpackhi_epi64(B0, _mm_unpacklo_epi64(B1, B1)); \
|
||||||
|
B1 = _mm_unpackhi_epi64(B1, _mm_unpacklo_epi64(t1, t1)); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
__m128i t0, t1; \
|
||||||
|
t0 = C0; \
|
||||||
|
C0 = C1; \
|
||||||
|
C1 = t0; \
|
||||||
|
t0 = B0; \
|
||||||
|
t1 = D0; \
|
||||||
|
B0 = _mm_unpackhi_epi64(B1, _mm_unpacklo_epi64(B0, B0)); \
|
||||||
|
B1 = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(B1, B1)); \
|
||||||
|
D0 = _mm_unpackhi_epi64(D0, _mm_unpacklo_epi64(D1, D1)); \
|
||||||
|
D1 = _mm_unpackhi_epi64(D1, _mm_unpacklo_epi64(t1, t1)); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define BLAKE2_ROUND(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
do { \
|
||||||
|
G1(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
G2(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
\
|
||||||
|
DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
\
|
||||||
|
G1(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
G2(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
\
|
||||||
|
UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
#else /* __AVX2__ */
|
||||||
|
|
||||||
|
#include <immintrin.h>
|
||||||
|
|
||||||
|
#define rotr32(x) _mm256_shuffle_epi32(x, _MM_SHUFFLE(2, 3, 0, 1))
|
||||||
|
#define rotr24(x) _mm256_shuffle_epi8(x, _mm256_setr_epi8(3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10, 3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10))
|
||||||
|
#define rotr16(x) _mm256_shuffle_epi8(x, _mm256_setr_epi8(2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9, 2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9))
|
||||||
|
#define rotr63(x) _mm256_xor_si256(_mm256_srli_epi64((x), 63), _mm256_add_epi64((x), (x)))
|
||||||
|
|
||||||
|
#define G1_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
do { \
|
||||||
|
__m256i ml = _mm256_mul_epu32(A0, B0); \
|
||||||
|
ml = _mm256_add_epi64(ml, ml); \
|
||||||
|
A0 = _mm256_add_epi64(A0, _mm256_add_epi64(B0, ml)); \
|
||||||
|
D0 = _mm256_xor_si256(D0, A0); \
|
||||||
|
D0 = rotr32(D0); \
|
||||||
|
\
|
||||||
|
ml = _mm256_mul_epu32(C0, D0); \
|
||||||
|
ml = _mm256_add_epi64(ml, ml); \
|
||||||
|
C0 = _mm256_add_epi64(C0, _mm256_add_epi64(D0, ml)); \
|
||||||
|
\
|
||||||
|
B0 = _mm256_xor_si256(B0, C0); \
|
||||||
|
B0 = rotr24(B0); \
|
||||||
|
\
|
||||||
|
ml = _mm256_mul_epu32(A1, B1); \
|
||||||
|
ml = _mm256_add_epi64(ml, ml); \
|
||||||
|
A1 = _mm256_add_epi64(A1, _mm256_add_epi64(B1, ml)); \
|
||||||
|
D1 = _mm256_xor_si256(D1, A1); \
|
||||||
|
D1 = rotr32(D1); \
|
||||||
|
\
|
||||||
|
ml = _mm256_mul_epu32(C1, D1); \
|
||||||
|
ml = _mm256_add_epi64(ml, ml); \
|
||||||
|
C1 = _mm256_add_epi64(C1, _mm256_add_epi64(D1, ml)); \
|
||||||
|
\
|
||||||
|
B1 = _mm256_xor_si256(B1, C1); \
|
||||||
|
B1 = rotr24(B1); \
|
||||||
|
} while((void)0, 0);
|
||||||
|
|
||||||
|
#define G2_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
do { \
|
||||||
|
__m256i ml = _mm256_mul_epu32(A0, B0); \
|
||||||
|
ml = _mm256_add_epi64(ml, ml); \
|
||||||
|
A0 = _mm256_add_epi64(A0, _mm256_add_epi64(B0, ml)); \
|
||||||
|
D0 = _mm256_xor_si256(D0, A0); \
|
||||||
|
D0 = rotr16(D0); \
|
||||||
|
\
|
||||||
|
ml = _mm256_mul_epu32(C0, D0); \
|
||||||
|
ml = _mm256_add_epi64(ml, ml); \
|
||||||
|
C0 = _mm256_add_epi64(C0, _mm256_add_epi64(D0, ml)); \
|
||||||
|
B0 = _mm256_xor_si256(B0, C0); \
|
||||||
|
B0 = rotr63(B0); \
|
||||||
|
\
|
||||||
|
ml = _mm256_mul_epu32(A1, B1); \
|
||||||
|
ml = _mm256_add_epi64(ml, ml); \
|
||||||
|
A1 = _mm256_add_epi64(A1, _mm256_add_epi64(B1, ml)); \
|
||||||
|
D1 = _mm256_xor_si256(D1, A1); \
|
||||||
|
D1 = rotr16(D1); \
|
||||||
|
\
|
||||||
|
ml = _mm256_mul_epu32(C1, D1); \
|
||||||
|
ml = _mm256_add_epi64(ml, ml); \
|
||||||
|
C1 = _mm256_add_epi64(C1, _mm256_add_epi64(D1, ml)); \
|
||||||
|
B1 = _mm256_xor_si256(B1, C1); \
|
||||||
|
B1 = rotr63(B1); \
|
||||||
|
} while((void)0, 0);
|
||||||
|
|
||||||
|
#define DIAGONALIZE_1(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
B0 = _mm256_permute4x64_epi64(B0, _MM_SHUFFLE(0, 3, 2, 1)); \
|
||||||
|
C0 = _mm256_permute4x64_epi64(C0, _MM_SHUFFLE(1, 0, 3, 2)); \
|
||||||
|
D0 = _mm256_permute4x64_epi64(D0, _MM_SHUFFLE(2, 1, 0, 3)); \
|
||||||
|
\
|
||||||
|
B1 = _mm256_permute4x64_epi64(B1, _MM_SHUFFLE(0, 3, 2, 1)); \
|
||||||
|
C1 = _mm256_permute4x64_epi64(C1, _MM_SHUFFLE(1, 0, 3, 2)); \
|
||||||
|
D1 = _mm256_permute4x64_epi64(D1, _MM_SHUFFLE(2, 1, 0, 3)); \
|
||||||
|
} while((void)0, 0);
|
||||||
|
|
||||||
|
#define DIAGONALIZE_2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
do { \
|
||||||
|
__m256i tmp1 = _mm256_blend_epi32(B0, B1, 0xCC); \
|
||||||
|
__m256i tmp2 = _mm256_blend_epi32(B0, B1, 0x33); \
|
||||||
|
B1 = _mm256_permute4x64_epi64(tmp1, _MM_SHUFFLE(2,3,0,1)); \
|
||||||
|
B0 = _mm256_permute4x64_epi64(tmp2, _MM_SHUFFLE(2,3,0,1)); \
|
||||||
|
\
|
||||||
|
tmp1 = C0; \
|
||||||
|
C0 = C1; \
|
||||||
|
C1 = tmp1; \
|
||||||
|
\
|
||||||
|
tmp1 = _mm256_blend_epi32(D0, D1, 0xCC); \
|
||||||
|
tmp2 = _mm256_blend_epi32(D0, D1, 0x33); \
|
||||||
|
D0 = _mm256_permute4x64_epi64(tmp1, _MM_SHUFFLE(2,3,0,1)); \
|
||||||
|
D1 = _mm256_permute4x64_epi64(tmp2, _MM_SHUFFLE(2,3,0,1)); \
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
#define UNDIAGONALIZE_1(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
B0 = _mm256_permute4x64_epi64(B0, _MM_SHUFFLE(2, 1, 0, 3)); \
|
||||||
|
C0 = _mm256_permute4x64_epi64(C0, _MM_SHUFFLE(1, 0, 3, 2)); \
|
||||||
|
D0 = _mm256_permute4x64_epi64(D0, _MM_SHUFFLE(0, 3, 2, 1)); \
|
||||||
|
\
|
||||||
|
B1 = _mm256_permute4x64_epi64(B1, _MM_SHUFFLE(2, 1, 0, 3)); \
|
||||||
|
C1 = _mm256_permute4x64_epi64(C1, _MM_SHUFFLE(1, 0, 3, 2)); \
|
||||||
|
D1 = _mm256_permute4x64_epi64(D1, _MM_SHUFFLE(0, 3, 2, 1)); \
|
||||||
|
} while((void)0, 0);
|
||||||
|
|
||||||
|
#define UNDIAGONALIZE_2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
do { \
|
||||||
|
__m256i tmp1 = _mm256_blend_epi32(B0, B1, 0xCC); \
|
||||||
|
__m256i tmp2 = _mm256_blend_epi32(B0, B1, 0x33); \
|
||||||
|
B0 = _mm256_permute4x64_epi64(tmp1, _MM_SHUFFLE(2,3,0,1)); \
|
||||||
|
B1 = _mm256_permute4x64_epi64(tmp2, _MM_SHUFFLE(2,3,0,1)); \
|
||||||
|
\
|
||||||
|
tmp1 = C0; \
|
||||||
|
C0 = C1; \
|
||||||
|
C1 = tmp1; \
|
||||||
|
\
|
||||||
|
tmp1 = _mm256_blend_epi32(D0, D1, 0x33); \
|
||||||
|
tmp2 = _mm256_blend_epi32(D0, D1, 0xCC); \
|
||||||
|
D0 = _mm256_permute4x64_epi64(tmp1, _MM_SHUFFLE(2,3,0,1)); \
|
||||||
|
D1 = _mm256_permute4x64_epi64(tmp2, _MM_SHUFFLE(2,3,0,1)); \
|
||||||
|
} while((void)0, 0);
|
||||||
|
|
||||||
|
#define BLAKE2_ROUND_1(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
do{ \
|
||||||
|
G1_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
G2_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
\
|
||||||
|
DIAGONALIZE_1(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
\
|
||||||
|
G1_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
G2_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
\
|
||||||
|
UNDIAGONALIZE_1(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
} while((void)0, 0);
|
||||||
|
|
||||||
|
#define BLAKE2_ROUND_2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
do{ \
|
||||||
|
G1_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
G2_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
\
|
||||||
|
DIAGONALIZE_2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
\
|
||||||
|
G1_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
G2_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
\
|
||||||
|
UNDIAGONALIZE_2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
} while((void)0, 0);
|
||||||
|
|
||||||
|
#endif /* __AVX2__ */
|
||||||
|
|
||||||
|
#else /* __AVX512F__ */
|
||||||
|
|
||||||
|
#include <immintrin.h>
|
||||||
|
|
||||||
|
#define ror64(x, n) _mm512_ror_epi64((x), (n))
|
||||||
|
|
||||||
|
static __m512i muladd(__m512i x, __m512i y)
|
||||||
|
{
|
||||||
|
__m512i z = _mm512_mul_epu32(x, y);
|
||||||
|
return _mm512_add_epi64(_mm512_add_epi64(x, y), _mm512_add_epi64(z, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
#define G1(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
A0 = muladd(A0, B0); \
|
||||||
|
A1 = muladd(A1, B1); \
|
||||||
|
\
|
||||||
|
D0 = _mm512_xor_si512(D0, A0); \
|
||||||
|
D1 = _mm512_xor_si512(D1, A1); \
|
||||||
|
\
|
||||||
|
D0 = ror64(D0, 32); \
|
||||||
|
D1 = ror64(D1, 32); \
|
||||||
|
\
|
||||||
|
C0 = muladd(C0, D0); \
|
||||||
|
C1 = muladd(C1, D1); \
|
||||||
|
\
|
||||||
|
B0 = _mm512_xor_si512(B0, C0); \
|
||||||
|
B1 = _mm512_xor_si512(B1, C1); \
|
||||||
|
\
|
||||||
|
B0 = ror64(B0, 24); \
|
||||||
|
B1 = ror64(B1, 24); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define G2(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
A0 = muladd(A0, B0); \
|
||||||
|
A1 = muladd(A1, B1); \
|
||||||
|
\
|
||||||
|
D0 = _mm512_xor_si512(D0, A0); \
|
||||||
|
D1 = _mm512_xor_si512(D1, A1); \
|
||||||
|
\
|
||||||
|
D0 = ror64(D0, 16); \
|
||||||
|
D1 = ror64(D1, 16); \
|
||||||
|
\
|
||||||
|
C0 = muladd(C0, D0); \
|
||||||
|
C1 = muladd(C1, D1); \
|
||||||
|
\
|
||||||
|
B0 = _mm512_xor_si512(B0, C0); \
|
||||||
|
B1 = _mm512_xor_si512(B1, C1); \
|
||||||
|
\
|
||||||
|
B0 = ror64(B0, 63); \
|
||||||
|
B1 = ror64(B1, 63); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
B0 = _mm512_permutex_epi64(B0, _MM_SHUFFLE(0, 3, 2, 1)); \
|
||||||
|
B1 = _mm512_permutex_epi64(B1, _MM_SHUFFLE(0, 3, 2, 1)); \
|
||||||
|
\
|
||||||
|
C0 = _mm512_permutex_epi64(C0, _MM_SHUFFLE(1, 0, 3, 2)); \
|
||||||
|
C1 = _mm512_permutex_epi64(C1, _MM_SHUFFLE(1, 0, 3, 2)); \
|
||||||
|
\
|
||||||
|
D0 = _mm512_permutex_epi64(D0, _MM_SHUFFLE(2, 1, 0, 3)); \
|
||||||
|
D1 = _mm512_permutex_epi64(D1, _MM_SHUFFLE(2, 1, 0, 3)); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
B0 = _mm512_permutex_epi64(B0, _MM_SHUFFLE(2, 1, 0, 3)); \
|
||||||
|
B1 = _mm512_permutex_epi64(B1, _MM_SHUFFLE(2, 1, 0, 3)); \
|
||||||
|
\
|
||||||
|
C0 = _mm512_permutex_epi64(C0, _MM_SHUFFLE(1, 0, 3, 2)); \
|
||||||
|
C1 = _mm512_permutex_epi64(C1, _MM_SHUFFLE(1, 0, 3, 2)); \
|
||||||
|
\
|
||||||
|
D0 = _mm512_permutex_epi64(D0, _MM_SHUFFLE(0, 3, 2, 1)); \
|
||||||
|
D1 = _mm512_permutex_epi64(D1, _MM_SHUFFLE(0, 3, 2, 1)); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define BLAKE2_ROUND(A0, B0, C0, D0, A1, B1, C1, D1) \
|
||||||
|
do { \
|
||||||
|
G1(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
G2(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
\
|
||||||
|
DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
\
|
||||||
|
G1(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
G2(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
\
|
||||||
|
UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define SWAP_HALVES(A0, A1) \
|
||||||
|
do { \
|
||||||
|
__m512i t0, t1; \
|
||||||
|
t0 = _mm512_shuffle_i64x2(A0, A1, _MM_SHUFFLE(1, 0, 1, 0)); \
|
||||||
|
t1 = _mm512_shuffle_i64x2(A0, A1, _MM_SHUFFLE(3, 2, 3, 2)); \
|
||||||
|
A0 = t0; \
|
||||||
|
A1 = t1; \
|
||||||
|
} while((void)0, 0)
|
||||||
|
|
||||||
|
#define SWAP_QUARTERS(A0, A1) \
|
||||||
|
do { \
|
||||||
|
SWAP_HALVES(A0, A1); \
|
||||||
|
A0 = _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 1, 4, 5, 2, 3, 6, 7), A0); \
|
||||||
|
A1 = _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 1, 4, 5, 2, 3, 6, 7), A1); \
|
||||||
|
} while((void)0, 0)
|
||||||
|
|
||||||
|
#define UNSWAP_QUARTERS(A0, A1) \
|
||||||
|
do { \
|
||||||
|
A0 = _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 1, 4, 5, 2, 3, 6, 7), A0); \
|
||||||
|
A1 = _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 1, 4, 5, 2, 3, 6, 7), A1); \
|
||||||
|
SWAP_HALVES(A0, A1); \
|
||||||
|
} while((void)0, 0)
|
||||||
|
|
||||||
|
#define BLAKE2_ROUND_1(A0, C0, B0, D0, A1, C1, B1, D1) \
|
||||||
|
do { \
|
||||||
|
SWAP_HALVES(A0, B0); \
|
||||||
|
SWAP_HALVES(C0, D0); \
|
||||||
|
SWAP_HALVES(A1, B1); \
|
||||||
|
SWAP_HALVES(C1, D1); \
|
||||||
|
BLAKE2_ROUND(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
SWAP_HALVES(A0, B0); \
|
||||||
|
SWAP_HALVES(C0, D0); \
|
||||||
|
SWAP_HALVES(A1, B1); \
|
||||||
|
SWAP_HALVES(C1, D1); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define BLAKE2_ROUND_2(A0, A1, B0, B1, C0, C1, D0, D1) \
|
||||||
|
do { \
|
||||||
|
SWAP_QUARTERS(A0, A1); \
|
||||||
|
SWAP_QUARTERS(B0, B1); \
|
||||||
|
SWAP_QUARTERS(C0, C1); \
|
||||||
|
SWAP_QUARTERS(D0, D1); \
|
||||||
|
BLAKE2_ROUND(A0, B0, C0, D0, A1, B1, C1, D1); \
|
||||||
|
UNSWAP_QUARTERS(A0, A1); \
|
||||||
|
UNSWAP_QUARTERS(B0, B1); \
|
||||||
|
UNSWAP_QUARTERS(C0, C1); \
|
||||||
|
UNSWAP_QUARTERS(D0, D1); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#endif /* __AVX512F__ */
|
||||||
|
#endif /* BLAKE_ROUND_MKA_OPT_H */
|
||||||
56
android/app/src/main/jni/src/blake2/blamka-round-ref.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BLAKE_ROUND_MKA_H
|
||||||
|
#define BLAKE_ROUND_MKA_H
|
||||||
|
|
||||||
|
#include "blake2.h"
|
||||||
|
#include "blake2-impl.h"
|
||||||
|
|
||||||
|
/* designed by the Lyra PHC team */
|
||||||
|
static BLAKE2_INLINE uint64_t fBlaMka(uint64_t x, uint64_t y) {
|
||||||
|
const uint64_t m = UINT64_C(0xFFFFFFFF);
|
||||||
|
const uint64_t xy = (x & m) * (y & m);
|
||||||
|
return x + y + 2 * xy;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define G(a, b, c, d) \
|
||||||
|
do { \
|
||||||
|
a = fBlaMka(a, b); \
|
||||||
|
d = rotr64(d ^ a, 32); \
|
||||||
|
c = fBlaMka(c, d); \
|
||||||
|
b = rotr64(b ^ c, 24); \
|
||||||
|
a = fBlaMka(a, b); \
|
||||||
|
d = rotr64(d ^ a, 16); \
|
||||||
|
c = fBlaMka(c, d); \
|
||||||
|
b = rotr64(b ^ c, 63); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define BLAKE2_ROUND_NOMSG(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, \
|
||||||
|
v12, v13, v14, v15) \
|
||||||
|
do { \
|
||||||
|
G(v0, v4, v8, v12); \
|
||||||
|
G(v1, v5, v9, v13); \
|
||||||
|
G(v2, v6, v10, v14); \
|
||||||
|
G(v3, v7, v11, v15); \
|
||||||
|
G(v0, v5, v10, v15); \
|
||||||
|
G(v1, v6, v11, v12); \
|
||||||
|
G(v2, v7, v8, v13); \
|
||||||
|
G(v3, v4, v9, v14); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#endif
|
||||||
390
android/app/src/main/jni/src/blake2b.c
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "blake2.h"
|
||||||
|
#include "blake2-impl.h"
|
||||||
|
|
||||||
|
static const uint64_t blake2b_IV[8] = {
|
||||||
|
UINT64_C(0x6a09e667f3bcc908), UINT64_C(0xbb67ae8584caa73b),
|
||||||
|
UINT64_C(0x3c6ef372fe94f82b), UINT64_C(0xa54ff53a5f1d36f1),
|
||||||
|
UINT64_C(0x510e527fade682d1), UINT64_C(0x9b05688c2b3e6c1f),
|
||||||
|
UINT64_C(0x1f83d9abfb41bd6b), UINT64_C(0x5be0cd19137e2179)};
|
||||||
|
|
||||||
|
static const unsigned int blake2b_sigma[12][16] = {
|
||||||
|
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||||
|
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
|
||||||
|
{11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
|
||||||
|
{7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
|
||||||
|
{9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
|
||||||
|
{2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
|
||||||
|
{12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
|
||||||
|
{13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
|
||||||
|
{6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
|
||||||
|
{10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0},
|
||||||
|
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||||
|
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
|
||||||
|
};
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void blake2b_set_lastnode(blake2b_state *S) {
|
||||||
|
S->f[1] = (uint64_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void blake2b_set_lastblock(blake2b_state *S) {
|
||||||
|
if (S->last_node) {
|
||||||
|
blake2b_set_lastnode(S);
|
||||||
|
}
|
||||||
|
S->f[0] = (uint64_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void blake2b_increment_counter(blake2b_state *S,
|
||||||
|
uint64_t inc) {
|
||||||
|
S->t[0] += inc;
|
||||||
|
S->t[1] += (S->t[0] < inc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void blake2b_invalidate_state(blake2b_state *S) {
|
||||||
|
clear_internal_memory(S, sizeof(*S)); /* wipe */
|
||||||
|
blake2b_set_lastblock(S); /* invalidate for further use */
|
||||||
|
}
|
||||||
|
|
||||||
|
static BLAKE2_INLINE void blake2b_init0(blake2b_state *S) {
|
||||||
|
memset(S, 0, sizeof(*S));
|
||||||
|
memcpy(S->h, blake2b_IV, sizeof(S->h));
|
||||||
|
}
|
||||||
|
|
||||||
|
int blake2b_init_param(blake2b_state *S, const blake2b_param *P) {
|
||||||
|
const unsigned char *p = (const unsigned char *)P;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
if (NULL == P || NULL == S) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
blake2b_init0(S);
|
||||||
|
/* IV XOR Parameter Block */
|
||||||
|
for (i = 0; i < 8; ++i) {
|
||||||
|
S->h[i] ^= load64(&p[i * sizeof(S->h[i])]);
|
||||||
|
}
|
||||||
|
S->outlen = P->digest_length;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sequential blake2b initialization */
|
||||||
|
int blake2b_init(blake2b_state *S, size_t outlen) {
|
||||||
|
blake2b_param P;
|
||||||
|
|
||||||
|
if (S == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) {
|
||||||
|
blake2b_invalidate_state(S);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup Parameter Block for unkeyed BLAKE2 */
|
||||||
|
P.digest_length = (uint8_t)outlen;
|
||||||
|
P.key_length = 0;
|
||||||
|
P.fanout = 1;
|
||||||
|
P.depth = 1;
|
||||||
|
P.leaf_length = 0;
|
||||||
|
P.node_offset = 0;
|
||||||
|
P.node_depth = 0;
|
||||||
|
P.inner_length = 0;
|
||||||
|
memset(P.reserved, 0, sizeof(P.reserved));
|
||||||
|
memset(P.salt, 0, sizeof(P.salt));
|
||||||
|
memset(P.personal, 0, sizeof(P.personal));
|
||||||
|
|
||||||
|
return blake2b_init_param(S, &P);
|
||||||
|
}
|
||||||
|
|
||||||
|
int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key,
|
||||||
|
size_t keylen) {
|
||||||
|
blake2b_param P;
|
||||||
|
|
||||||
|
if (S == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) {
|
||||||
|
blake2b_invalidate_state(S);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((key == 0) || (keylen == 0) || (keylen > BLAKE2B_KEYBYTES)) {
|
||||||
|
blake2b_invalidate_state(S);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup Parameter Block for keyed BLAKE2 */
|
||||||
|
P.digest_length = (uint8_t)outlen;
|
||||||
|
P.key_length = (uint8_t)keylen;
|
||||||
|
P.fanout = 1;
|
||||||
|
P.depth = 1;
|
||||||
|
P.leaf_length = 0;
|
||||||
|
P.node_offset = 0;
|
||||||
|
P.node_depth = 0;
|
||||||
|
P.inner_length = 0;
|
||||||
|
memset(P.reserved, 0, sizeof(P.reserved));
|
||||||
|
memset(P.salt, 0, sizeof(P.salt));
|
||||||
|
memset(P.personal, 0, sizeof(P.personal));
|
||||||
|
|
||||||
|
if (blake2b_init_param(S, &P) < 0) {
|
||||||
|
blake2b_invalidate_state(S);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
uint8_t block[BLAKE2B_BLOCKBYTES];
|
||||||
|
memset(block, 0, BLAKE2B_BLOCKBYTES);
|
||||||
|
memcpy(block, key, keylen);
|
||||||
|
blake2b_update(S, block, BLAKE2B_BLOCKBYTES);
|
||||||
|
/* Burn the key from stack */
|
||||||
|
clear_internal_memory(block, BLAKE2B_BLOCKBYTES);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void blake2b_compress(blake2b_state *S, const uint8_t *block) {
|
||||||
|
uint64_t m[16];
|
||||||
|
uint64_t v[16];
|
||||||
|
unsigned int i, r;
|
||||||
|
|
||||||
|
for (i = 0; i < 16; ++i) {
|
||||||
|
m[i] = load64(block + i * sizeof(m[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 8; ++i) {
|
||||||
|
v[i] = S->h[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
v[8] = blake2b_IV[0];
|
||||||
|
v[9] = blake2b_IV[1];
|
||||||
|
v[10] = blake2b_IV[2];
|
||||||
|
v[11] = blake2b_IV[3];
|
||||||
|
v[12] = blake2b_IV[4] ^ S->t[0];
|
||||||
|
v[13] = blake2b_IV[5] ^ S->t[1];
|
||||||
|
v[14] = blake2b_IV[6] ^ S->f[0];
|
||||||
|
v[15] = blake2b_IV[7] ^ S->f[1];
|
||||||
|
|
||||||
|
#define G(r, i, a, b, c, d) \
|
||||||
|
do { \
|
||||||
|
a = a + b + m[blake2b_sigma[r][2 * i + 0]]; \
|
||||||
|
d = rotr64(d ^ a, 32); \
|
||||||
|
c = c + d; \
|
||||||
|
b = rotr64(b ^ c, 24); \
|
||||||
|
a = a + b + m[blake2b_sigma[r][2 * i + 1]]; \
|
||||||
|
d = rotr64(d ^ a, 16); \
|
||||||
|
c = c + d; \
|
||||||
|
b = rotr64(b ^ c, 63); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define ROUND(r) \
|
||||||
|
do { \
|
||||||
|
G(r, 0, v[0], v[4], v[8], v[12]); \
|
||||||
|
G(r, 1, v[1], v[5], v[9], v[13]); \
|
||||||
|
G(r, 2, v[2], v[6], v[10], v[14]); \
|
||||||
|
G(r, 3, v[3], v[7], v[11], v[15]); \
|
||||||
|
G(r, 4, v[0], v[5], v[10], v[15]); \
|
||||||
|
G(r, 5, v[1], v[6], v[11], v[12]); \
|
||||||
|
G(r, 6, v[2], v[7], v[8], v[13]); \
|
||||||
|
G(r, 7, v[3], v[4], v[9], v[14]); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
for (r = 0; r < 12; ++r) {
|
||||||
|
ROUND(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 8; ++i) {
|
||||||
|
S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef G
|
||||||
|
#undef ROUND
|
||||||
|
}
|
||||||
|
|
||||||
|
int blake2b_update(blake2b_state *S, const void *in, size_t inlen) {
|
||||||
|
const uint8_t *pin = (const uint8_t *)in;
|
||||||
|
|
||||||
|
if (inlen == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sanity check */
|
||||||
|
if (S == NULL || in == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Is this a reused state? */
|
||||||
|
if (S->f[0] != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (S->buflen + inlen > BLAKE2B_BLOCKBYTES) {
|
||||||
|
/* Complete current block */
|
||||||
|
size_t left = S->buflen;
|
||||||
|
size_t fill = BLAKE2B_BLOCKBYTES - left;
|
||||||
|
memcpy(&S->buf[left], pin, fill);
|
||||||
|
blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
|
||||||
|
blake2b_compress(S, S->buf);
|
||||||
|
S->buflen = 0;
|
||||||
|
inlen -= fill;
|
||||||
|
pin += fill;
|
||||||
|
/* Avoid buffer copies when possible */
|
||||||
|
while (inlen > BLAKE2B_BLOCKBYTES) {
|
||||||
|
blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
|
||||||
|
blake2b_compress(S, pin);
|
||||||
|
inlen -= BLAKE2B_BLOCKBYTES;
|
||||||
|
pin += BLAKE2B_BLOCKBYTES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(&S->buf[S->buflen], pin, inlen);
|
||||||
|
S->buflen += (unsigned int)inlen;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int blake2b_final(blake2b_state *S, void *out, size_t outlen) {
|
||||||
|
uint8_t buffer[BLAKE2B_OUTBYTES] = {0};
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
/* Sanity checks */
|
||||||
|
if (S == NULL || out == NULL || outlen < S->outlen) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Is this a reused state? */
|
||||||
|
if (S->f[0] != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
blake2b_increment_counter(S, S->buflen);
|
||||||
|
blake2b_set_lastblock(S);
|
||||||
|
memset(&S->buf[S->buflen], 0, BLAKE2B_BLOCKBYTES - S->buflen); /* Padding */
|
||||||
|
blake2b_compress(S, S->buf);
|
||||||
|
|
||||||
|
for (i = 0; i < 8; ++i) { /* Output full hash to temp buffer */
|
||||||
|
store64(buffer + sizeof(S->h[i]) * i, S->h[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(out, buffer, S->outlen);
|
||||||
|
clear_internal_memory(buffer, sizeof(buffer));
|
||||||
|
clear_internal_memory(S->buf, sizeof(S->buf));
|
||||||
|
clear_internal_memory(S->h, sizeof(S->h));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int blake2b(void *out, size_t outlen, const void *in, size_t inlen,
|
||||||
|
const void *key, size_t keylen) {
|
||||||
|
blake2b_state S;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
/* Verify parameters */
|
||||||
|
if (NULL == in && inlen > 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NULL == out || outlen == 0 || outlen > BLAKE2B_OUTBYTES) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((NULL == key && keylen > 0) || keylen > BLAKE2B_KEYBYTES) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keylen > 0) {
|
||||||
|
if (blake2b_init_key(&S, outlen, key, keylen) < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (blake2b_init(&S, outlen) < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blake2b_update(&S, in, inlen) < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
ret = blake2b_final(&S, out, outlen);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
clear_internal_memory(&S, sizeof(S));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Argon2 Team - Begin Code */
|
||||||
|
int blake2b_long(void *pout, size_t outlen, const void *in, size_t inlen) {
|
||||||
|
uint8_t *out = (uint8_t *)pout;
|
||||||
|
blake2b_state blake_state;
|
||||||
|
uint8_t outlen_bytes[sizeof(uint32_t)] = {0};
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
if (outlen > UINT32_MAX) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure little-endian byte order! */
|
||||||
|
store32(outlen_bytes, (uint32_t)outlen);
|
||||||
|
|
||||||
|
#define TRY(statement) \
|
||||||
|
do { \
|
||||||
|
ret = statement; \
|
||||||
|
if (ret < 0) { \
|
||||||
|
goto fail; \
|
||||||
|
} \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
if (outlen <= BLAKE2B_OUTBYTES) {
|
||||||
|
TRY(blake2b_init(&blake_state, outlen));
|
||||||
|
TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
|
||||||
|
TRY(blake2b_update(&blake_state, in, inlen));
|
||||||
|
TRY(blake2b_final(&blake_state, out, outlen));
|
||||||
|
} else {
|
||||||
|
uint32_t toproduce;
|
||||||
|
uint8_t out_buffer[BLAKE2B_OUTBYTES];
|
||||||
|
uint8_t in_buffer[BLAKE2B_OUTBYTES];
|
||||||
|
TRY(blake2b_init(&blake_state, BLAKE2B_OUTBYTES));
|
||||||
|
TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
|
||||||
|
TRY(blake2b_update(&blake_state, in, inlen));
|
||||||
|
TRY(blake2b_final(&blake_state, out_buffer, BLAKE2B_OUTBYTES));
|
||||||
|
memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2);
|
||||||
|
out += BLAKE2B_OUTBYTES / 2;
|
||||||
|
toproduce = (uint32_t)outlen - BLAKE2B_OUTBYTES / 2;
|
||||||
|
|
||||||
|
while (toproduce > BLAKE2B_OUTBYTES) {
|
||||||
|
memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES);
|
||||||
|
TRY(blake2b(out_buffer, BLAKE2B_OUTBYTES, in_buffer,
|
||||||
|
BLAKE2B_OUTBYTES, NULL, 0));
|
||||||
|
memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2);
|
||||||
|
out += BLAKE2B_OUTBYTES / 2;
|
||||||
|
toproduce -= BLAKE2B_OUTBYTES / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES);
|
||||||
|
TRY(blake2b(out_buffer, toproduce, in_buffer, BLAKE2B_OUTBYTES, NULL,
|
||||||
|
0));
|
||||||
|
memcpy(out, out_buffer, toproduce);
|
||||||
|
}
|
||||||
|
fail:
|
||||||
|
clear_internal_memory(&blake_state, sizeof(blake_state));
|
||||||
|
return ret;
|
||||||
|
#undef TRY
|
||||||
|
}
|
||||||
|
/* Argon2 Team - End Code */
|
||||||
648
android/app/src/main/jni/src/core.c
Normal file
@@ -0,0 +1,648 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*For memory wiping*/
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winbase.h> /* For SecureZeroMemory */
|
||||||
|
#endif
|
||||||
|
#if defined __STDC_LIB_EXT1__
|
||||||
|
#define __STDC_WANT_LIB_EXT1__ 1
|
||||||
|
#endif
|
||||||
|
#define VC_GE_2005(version) (version >= 1400)
|
||||||
|
|
||||||
|
/* for explicit_bzero() on glibc */
|
||||||
|
#define _DEFAULT_SOURCE
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "blake2/blake2.h"
|
||||||
|
#include "blake2/blake2-impl.h"
|
||||||
|
|
||||||
|
#ifdef GENKAT
|
||||||
|
#include "genkat.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__clang__)
|
||||||
|
#if __has_attribute(optnone)
|
||||||
|
#define NOT_OPTIMIZED __attribute__((optnone))
|
||||||
|
#endif
|
||||||
|
#elif defined(__GNUC__)
|
||||||
|
#define GCC_VERSION \
|
||||||
|
(__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
|
||||||
|
#if GCC_VERSION >= 40400
|
||||||
|
#define NOT_OPTIMIZED __attribute__((optimize("O0")))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifndef NOT_OPTIMIZED
|
||||||
|
#define NOT_OPTIMIZED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/***************Instance and Position constructors**********/
|
||||||
|
void init_block_value(block *b, uint8_t in) { memset(b->v, in, sizeof(b->v)); }
|
||||||
|
|
||||||
|
void copy_block(block *dst, const block *src) {
|
||||||
|
memcpy(dst->v, src->v, sizeof(uint64_t) * ARGON2_QWORDS_IN_BLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void xor_block(block *dst, const block *src) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) {
|
||||||
|
dst->v[i] ^= src->v[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void load_block(block *dst, const void *input) {
|
||||||
|
unsigned i;
|
||||||
|
for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) {
|
||||||
|
dst->v[i] = load64((const uint8_t *)input + i * sizeof(dst->v[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void store_block(void *output, const block *src) {
|
||||||
|
unsigned i;
|
||||||
|
for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) {
|
||||||
|
store64((uint8_t *)output + i * sizeof(src->v[i]), src->v[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************Memory functions*****************/
|
||||||
|
|
||||||
|
int allocate_memory(const argon2_context *context, uint8_t **memory,
|
||||||
|
size_t num, size_t size) {
|
||||||
|
size_t memory_size = num*size;
|
||||||
|
if (memory == NULL) {
|
||||||
|
return ARGON2_MEMORY_ALLOCATION_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. Check for multiplication overflow */
|
||||||
|
if (size != 0 && memory_size / size != num) {
|
||||||
|
return ARGON2_MEMORY_ALLOCATION_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Try to allocate with appropriate allocator */
|
||||||
|
if (context->allocate_cbk) {
|
||||||
|
(context->allocate_cbk)(memory, memory_size);
|
||||||
|
} else {
|
||||||
|
*memory = malloc(memory_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*memory == NULL) {
|
||||||
|
return ARGON2_MEMORY_ALLOCATION_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ARGON2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_memory(const argon2_context *context, uint8_t *memory,
|
||||||
|
size_t num, size_t size) {
|
||||||
|
size_t memory_size = num*size;
|
||||||
|
clear_internal_memory(memory, memory_size);
|
||||||
|
if (context->free_cbk) {
|
||||||
|
(context->free_cbk)(memory, memory_size);
|
||||||
|
} else {
|
||||||
|
free(memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__OpenBSD__)
|
||||||
|
#define HAVE_EXPLICIT_BZERO 1
|
||||||
|
#elif defined(__GLIBC__) && defined(__GLIBC_PREREQ)
|
||||||
|
#if __GLIBC_PREREQ(2,25)
|
||||||
|
#define HAVE_EXPLICIT_BZERO 1
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void NOT_OPTIMIZED secure_wipe_memory(void *v, size_t n) {
|
||||||
|
#if defined(_MSC_VER) && VC_GE_2005(_MSC_VER) || defined(__MINGW32__)
|
||||||
|
SecureZeroMemory(v, n);
|
||||||
|
#elif defined memset_s
|
||||||
|
memset_s(v, n, 0, n);
|
||||||
|
#elif defined(HAVE_EXPLICIT_BZERO)
|
||||||
|
explicit_bzero(v, n);
|
||||||
|
#else
|
||||||
|
static void *(*const volatile memset_sec)(void *, int, size_t) = &memset;
|
||||||
|
memset_sec(v, 0, n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Memory clear flag defaults to true. */
|
||||||
|
int FLAG_clear_internal_memory = 1;
|
||||||
|
void clear_internal_memory(void *v, size_t n) {
|
||||||
|
if (FLAG_clear_internal_memory && v) {
|
||||||
|
secure_wipe_memory(v, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void finalize(const argon2_context *context, argon2_instance_t *instance) {
|
||||||
|
if (context != NULL && instance != NULL) {
|
||||||
|
block blockhash;
|
||||||
|
uint32_t l;
|
||||||
|
|
||||||
|
copy_block(&blockhash, instance->memory + instance->lane_length - 1);
|
||||||
|
|
||||||
|
/* XOR the last blocks */
|
||||||
|
for (l = 1; l < instance->lanes; ++l) {
|
||||||
|
uint32_t last_block_in_lane =
|
||||||
|
l * instance->lane_length + (instance->lane_length - 1);
|
||||||
|
xor_block(&blockhash, instance->memory + last_block_in_lane);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hash the result */
|
||||||
|
{
|
||||||
|
uint8_t blockhash_bytes[ARGON2_BLOCK_SIZE];
|
||||||
|
store_block(blockhash_bytes, &blockhash);
|
||||||
|
blake2b_long(context->out, context->outlen, blockhash_bytes,
|
||||||
|
ARGON2_BLOCK_SIZE);
|
||||||
|
/* clear blockhash and blockhash_bytes */
|
||||||
|
clear_internal_memory(blockhash.v, ARGON2_BLOCK_SIZE);
|
||||||
|
clear_internal_memory(blockhash_bytes, ARGON2_BLOCK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef GENKAT
|
||||||
|
print_tag(context->out, context->outlen);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
free_memory(context, (uint8_t *)instance->memory,
|
||||||
|
instance->memory_blocks, sizeof(block));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t index_alpha(const argon2_instance_t *instance,
|
||||||
|
const argon2_position_t *position, uint32_t pseudo_rand,
|
||||||
|
int same_lane) {
|
||||||
|
/*
|
||||||
|
* Pass 0:
|
||||||
|
* This lane : all already finished segments plus already constructed
|
||||||
|
* blocks in this segment
|
||||||
|
* Other lanes : all already finished segments
|
||||||
|
* Pass 1+:
|
||||||
|
* This lane : (SYNC_POINTS - 1) last segments plus already constructed
|
||||||
|
* blocks in this segment
|
||||||
|
* Other lanes : (SYNC_POINTS - 1) last segments
|
||||||
|
*/
|
||||||
|
uint32_t reference_area_size;
|
||||||
|
uint64_t relative_position;
|
||||||
|
uint32_t start_position, absolute_position;
|
||||||
|
|
||||||
|
if (0 == position->pass) {
|
||||||
|
/* First pass */
|
||||||
|
if (0 == position->slice) {
|
||||||
|
/* First slice */
|
||||||
|
reference_area_size =
|
||||||
|
position->index - 1; /* all but the previous */
|
||||||
|
} else {
|
||||||
|
if (same_lane) {
|
||||||
|
/* The same lane => add current segment */
|
||||||
|
reference_area_size =
|
||||||
|
position->slice * instance->segment_length +
|
||||||
|
position->index - 1;
|
||||||
|
} else {
|
||||||
|
reference_area_size =
|
||||||
|
position->slice * instance->segment_length +
|
||||||
|
((position->index == 0) ? (-1) : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Second pass */
|
||||||
|
if (same_lane) {
|
||||||
|
reference_area_size = instance->lane_length -
|
||||||
|
instance->segment_length + position->index -
|
||||||
|
1;
|
||||||
|
} else {
|
||||||
|
reference_area_size = instance->lane_length -
|
||||||
|
instance->segment_length +
|
||||||
|
((position->index == 0) ? (-1) : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1.2.4. Mapping pseudo_rand to 0..<reference_area_size-1> and produce
|
||||||
|
* relative position */
|
||||||
|
relative_position = pseudo_rand;
|
||||||
|
relative_position = relative_position * relative_position >> 32;
|
||||||
|
relative_position = reference_area_size - 1 -
|
||||||
|
(reference_area_size * relative_position >> 32);
|
||||||
|
|
||||||
|
/* 1.2.5 Computing starting position */
|
||||||
|
start_position = 0;
|
||||||
|
|
||||||
|
if (0 != position->pass) {
|
||||||
|
start_position = (position->slice == ARGON2_SYNC_POINTS - 1)
|
||||||
|
? 0
|
||||||
|
: (position->slice + 1) * instance->segment_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1.2.6. Computing absolute position */
|
||||||
|
absolute_position = (start_position + relative_position) %
|
||||||
|
instance->lane_length; /* absolute position */
|
||||||
|
return absolute_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Single-threaded version for p=1 case */
|
||||||
|
static int fill_memory_blocks_st(argon2_instance_t *instance) {
|
||||||
|
uint32_t r, s, l;
|
||||||
|
|
||||||
|
for (r = 0; r < instance->passes; ++r) {
|
||||||
|
for (s = 0; s < ARGON2_SYNC_POINTS; ++s) {
|
||||||
|
for (l = 0; l < instance->lanes; ++l) {
|
||||||
|
argon2_position_t position = {r, l, (uint8_t)s, 0};
|
||||||
|
fill_segment(instance, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef GENKAT
|
||||||
|
internal_kat(instance, r); /* Print all memory blocks */
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return ARGON2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(ARGON2_NO_THREADS)
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static unsigned __stdcall fill_segment_thr(void *thread_data)
|
||||||
|
#else
|
||||||
|
static void *fill_segment_thr(void *thread_data)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
argon2_thread_data *my_data = thread_data;
|
||||||
|
fill_segment(my_data->instance_ptr, my_data->pos);
|
||||||
|
argon2_thread_exit();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Multi-threaded version for p > 1 case */
|
||||||
|
static int fill_memory_blocks_mt(argon2_instance_t *instance) {
|
||||||
|
uint32_t r, s;
|
||||||
|
argon2_thread_handle_t *thread = NULL;
|
||||||
|
argon2_thread_data *thr_data = NULL;
|
||||||
|
int rc = ARGON2_OK;
|
||||||
|
|
||||||
|
/* 1. Allocating space for threads */
|
||||||
|
thread = calloc(instance->lanes, sizeof(argon2_thread_handle_t));
|
||||||
|
if (thread == NULL) {
|
||||||
|
rc = ARGON2_MEMORY_ALLOCATION_ERROR;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
thr_data = calloc(instance->lanes, sizeof(argon2_thread_data));
|
||||||
|
if (thr_data == NULL) {
|
||||||
|
rc = ARGON2_MEMORY_ALLOCATION_ERROR;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (r = 0; r < instance->passes; ++r) {
|
||||||
|
for (s = 0; s < ARGON2_SYNC_POINTS; ++s) {
|
||||||
|
uint32_t l, ll;
|
||||||
|
|
||||||
|
/* 2. Calling threads */
|
||||||
|
for (l = 0; l < instance->lanes; ++l) {
|
||||||
|
argon2_position_t position;
|
||||||
|
|
||||||
|
/* 2.1 Join a thread if limit is exceeded */
|
||||||
|
if (l >= instance->threads) {
|
||||||
|
if (argon2_thread_join(thread[l - instance->threads])) {
|
||||||
|
rc = ARGON2_THREAD_FAIL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2.2 Create thread */
|
||||||
|
position.pass = r;
|
||||||
|
position.lane = l;
|
||||||
|
position.slice = (uint8_t)s;
|
||||||
|
position.index = 0;
|
||||||
|
thr_data[l].instance_ptr =
|
||||||
|
instance; /* preparing the thread input */
|
||||||
|
memcpy(&(thr_data[l].pos), &position,
|
||||||
|
sizeof(argon2_position_t));
|
||||||
|
if (argon2_thread_create(&thread[l], &fill_segment_thr,
|
||||||
|
(void *)&thr_data[l])) {
|
||||||
|
/* Wait for already running threads */
|
||||||
|
for (ll = 0; ll < l; ++ll)
|
||||||
|
argon2_thread_join(thread[ll]);
|
||||||
|
rc = ARGON2_THREAD_FAIL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fill_segment(instance, position); */
|
||||||
|
/*Non-thread equivalent of the lines above */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Joining remaining threads */
|
||||||
|
for (l = instance->lanes - instance->threads; l < instance->lanes;
|
||||||
|
++l) {
|
||||||
|
if (argon2_thread_join(thread[l])) {
|
||||||
|
rc = ARGON2_THREAD_FAIL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef GENKAT
|
||||||
|
internal_kat(instance, r); /* Print all memory blocks */
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
fail:
|
||||||
|
if (thread != NULL) {
|
||||||
|
free(thread);
|
||||||
|
}
|
||||||
|
if (thr_data != NULL) {
|
||||||
|
free(thr_data);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ARGON2_NO_THREADS */
|
||||||
|
|
||||||
|
int fill_memory_blocks(argon2_instance_t *instance) {
|
||||||
|
if (instance == NULL || instance->lanes == 0) {
|
||||||
|
return ARGON2_INCORRECT_PARAMETER;
|
||||||
|
}
|
||||||
|
#if defined(ARGON2_NO_THREADS)
|
||||||
|
return fill_memory_blocks_st(instance);
|
||||||
|
#else
|
||||||
|
return instance->threads == 1 ?
|
||||||
|
fill_memory_blocks_st(instance) : fill_memory_blocks_mt(instance);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int validate_inputs(const argon2_context *context) {
|
||||||
|
if (NULL == context) {
|
||||||
|
return ARGON2_INCORRECT_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NULL == context->out) {
|
||||||
|
return ARGON2_OUTPUT_PTR_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate output length */
|
||||||
|
if (ARGON2_MIN_OUTLEN > context->outlen) {
|
||||||
|
return ARGON2_OUTPUT_TOO_SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ARGON2_MAX_OUTLEN < context->outlen) {
|
||||||
|
return ARGON2_OUTPUT_TOO_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate password (required param) */
|
||||||
|
if (NULL == context->pwd) {
|
||||||
|
if (0 != context->pwdlen) {
|
||||||
|
return ARGON2_PWD_PTR_MISMATCH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ARGON2_MIN_PWD_LENGTH > context->pwdlen) {
|
||||||
|
return ARGON2_PWD_TOO_SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ARGON2_MAX_PWD_LENGTH < context->pwdlen) {
|
||||||
|
return ARGON2_PWD_TOO_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate salt (required param) */
|
||||||
|
if (NULL == context->salt) {
|
||||||
|
if (0 != context->saltlen) {
|
||||||
|
return ARGON2_SALT_PTR_MISMATCH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ARGON2_MIN_SALT_LENGTH > context->saltlen) {
|
||||||
|
return ARGON2_SALT_TOO_SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ARGON2_MAX_SALT_LENGTH < context->saltlen) {
|
||||||
|
return ARGON2_SALT_TOO_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate secret (optional param) */
|
||||||
|
if (NULL == context->secret) {
|
||||||
|
if (0 != context->secretlen) {
|
||||||
|
return ARGON2_SECRET_PTR_MISMATCH;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ARGON2_MIN_SECRET > context->secretlen) {
|
||||||
|
return ARGON2_SECRET_TOO_SHORT;
|
||||||
|
}
|
||||||
|
if (ARGON2_MAX_SECRET < context->secretlen) {
|
||||||
|
return ARGON2_SECRET_TOO_LONG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate associated data (optional param) */
|
||||||
|
if (NULL == context->ad) {
|
||||||
|
if (0 != context->adlen) {
|
||||||
|
return ARGON2_AD_PTR_MISMATCH;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ARGON2_MIN_AD_LENGTH > context->adlen) {
|
||||||
|
return ARGON2_AD_TOO_SHORT;
|
||||||
|
}
|
||||||
|
if (ARGON2_MAX_AD_LENGTH < context->adlen) {
|
||||||
|
return ARGON2_AD_TOO_LONG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate memory cost */
|
||||||
|
if (ARGON2_MIN_MEMORY > context->m_cost) {
|
||||||
|
return ARGON2_MEMORY_TOO_LITTLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ARGON2_MAX_MEMORY < context->m_cost) {
|
||||||
|
return ARGON2_MEMORY_TOO_MUCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->m_cost < 8 * context->lanes) {
|
||||||
|
return ARGON2_MEMORY_TOO_LITTLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate time cost */
|
||||||
|
if (ARGON2_MIN_TIME > context->t_cost) {
|
||||||
|
return ARGON2_TIME_TOO_SMALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ARGON2_MAX_TIME < context->t_cost) {
|
||||||
|
return ARGON2_TIME_TOO_LARGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate lanes */
|
||||||
|
if (ARGON2_MIN_LANES > context->lanes) {
|
||||||
|
return ARGON2_LANES_TOO_FEW;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ARGON2_MAX_LANES < context->lanes) {
|
||||||
|
return ARGON2_LANES_TOO_MANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate threads */
|
||||||
|
if (ARGON2_MIN_THREADS > context->threads) {
|
||||||
|
return ARGON2_THREADS_TOO_FEW;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ARGON2_MAX_THREADS < context->threads) {
|
||||||
|
return ARGON2_THREADS_TOO_MANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NULL != context->allocate_cbk && NULL == context->free_cbk) {
|
||||||
|
return ARGON2_FREE_MEMORY_CBK_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NULL == context->allocate_cbk && NULL != context->free_cbk) {
|
||||||
|
return ARGON2_ALLOCATE_MEMORY_CBK_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ARGON2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fill_first_blocks(uint8_t *blockhash, const argon2_instance_t *instance) {
|
||||||
|
uint32_t l;
|
||||||
|
/* Make the first and second block in each lane as G(H0||0||i) or
|
||||||
|
G(H0||1||i) */
|
||||||
|
uint8_t blockhash_bytes[ARGON2_BLOCK_SIZE];
|
||||||
|
for (l = 0; l < instance->lanes; ++l) {
|
||||||
|
|
||||||
|
store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 0);
|
||||||
|
store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH + 4, l);
|
||||||
|
blake2b_long(blockhash_bytes, ARGON2_BLOCK_SIZE, blockhash,
|
||||||
|
ARGON2_PREHASH_SEED_LENGTH);
|
||||||
|
load_block(&instance->memory[l * instance->lane_length + 0],
|
||||||
|
blockhash_bytes);
|
||||||
|
|
||||||
|
store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 1);
|
||||||
|
blake2b_long(blockhash_bytes, ARGON2_BLOCK_SIZE, blockhash,
|
||||||
|
ARGON2_PREHASH_SEED_LENGTH);
|
||||||
|
load_block(&instance->memory[l * instance->lane_length + 1],
|
||||||
|
blockhash_bytes);
|
||||||
|
}
|
||||||
|
clear_internal_memory(blockhash_bytes, ARGON2_BLOCK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void initial_hash(uint8_t *blockhash, argon2_context *context,
|
||||||
|
argon2_type type) {
|
||||||
|
blake2b_state BlakeHash;
|
||||||
|
uint8_t value[sizeof(uint32_t)];
|
||||||
|
|
||||||
|
if (NULL == context || NULL == blockhash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
blake2b_init(&BlakeHash, ARGON2_PREHASH_DIGEST_LENGTH);
|
||||||
|
|
||||||
|
store32(&value, context->lanes);
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
|
||||||
|
|
||||||
|
store32(&value, context->outlen);
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
|
||||||
|
|
||||||
|
store32(&value, context->m_cost);
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
|
||||||
|
|
||||||
|
store32(&value, context->t_cost);
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
|
||||||
|
|
||||||
|
store32(&value, context->version);
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
|
||||||
|
|
||||||
|
store32(&value, (uint32_t)type);
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
|
||||||
|
|
||||||
|
store32(&value, context->pwdlen);
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
|
||||||
|
|
||||||
|
if (context->pwd != NULL) {
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)context->pwd,
|
||||||
|
context->pwdlen);
|
||||||
|
|
||||||
|
if (context->flags & ARGON2_FLAG_CLEAR_PASSWORD) {
|
||||||
|
secure_wipe_memory(context->pwd, context->pwdlen);
|
||||||
|
context->pwdlen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store32(&value, context->saltlen);
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
|
||||||
|
|
||||||
|
if (context->salt != NULL) {
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)context->salt,
|
||||||
|
context->saltlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
store32(&value, context->secretlen);
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
|
||||||
|
|
||||||
|
if (context->secret != NULL) {
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)context->secret,
|
||||||
|
context->secretlen);
|
||||||
|
|
||||||
|
if (context->flags & ARGON2_FLAG_CLEAR_SECRET) {
|
||||||
|
secure_wipe_memory(context->secret, context->secretlen);
|
||||||
|
context->secretlen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store32(&value, context->adlen);
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
|
||||||
|
|
||||||
|
if (context->ad != NULL) {
|
||||||
|
blake2b_update(&BlakeHash, (const uint8_t *)context->ad,
|
||||||
|
context->adlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
blake2b_final(&BlakeHash, blockhash, ARGON2_PREHASH_DIGEST_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
int initialize(argon2_instance_t *instance, argon2_context *context) {
|
||||||
|
uint8_t blockhash[ARGON2_PREHASH_SEED_LENGTH];
|
||||||
|
int result = ARGON2_OK;
|
||||||
|
|
||||||
|
if (instance == NULL || context == NULL)
|
||||||
|
return ARGON2_INCORRECT_PARAMETER;
|
||||||
|
instance->context_ptr = context;
|
||||||
|
|
||||||
|
/* 1. Memory allocation */
|
||||||
|
result = allocate_memory(context, (uint8_t **)&(instance->memory),
|
||||||
|
instance->memory_blocks, sizeof(block));
|
||||||
|
if (result != ARGON2_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Initial hashing */
|
||||||
|
/* H_0 + 8 extra bytes to produce the first blocks */
|
||||||
|
/* uint8_t blockhash[ARGON2_PREHASH_SEED_LENGTH]; */
|
||||||
|
/* Hashing all inputs */
|
||||||
|
initial_hash(blockhash, context, instance->type);
|
||||||
|
/* Zeroing 8 extra bytes */
|
||||||
|
clear_internal_memory(blockhash + ARGON2_PREHASH_DIGEST_LENGTH,
|
||||||
|
ARGON2_PREHASH_SEED_LENGTH -
|
||||||
|
ARGON2_PREHASH_DIGEST_LENGTH);
|
||||||
|
|
||||||
|
#ifdef GENKAT
|
||||||
|
initial_kat(blockhash, context, instance->type);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 3. Creating first blocks, we always have at least two blocks in a slice
|
||||||
|
*/
|
||||||
|
fill_first_blocks(blockhash, instance);
|
||||||
|
/* Clearing the hash */
|
||||||
|
clear_internal_memory(blockhash, ARGON2_PREHASH_SEED_LENGTH);
|
||||||
|
|
||||||
|
return ARGON2_OK;
|
||||||
|
}
|
||||||
463
android/app/src/main/jni/src/encoding.c
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include "encoding.h"
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Example code for a decoder and encoder of "hash strings", with Argon2
|
||||||
|
* parameters.
|
||||||
|
*
|
||||||
|
* This code comprises three sections:
|
||||||
|
*
|
||||||
|
* -- The first section contains generic Base64 encoding and decoding
|
||||||
|
* functions. It is conceptually applicable to any hash function
|
||||||
|
* implementation that uses Base64 to encode and decode parameters,
|
||||||
|
* salts and outputs. It could be made into a library, provided that
|
||||||
|
* the relevant functions are made public (non-static) and be given
|
||||||
|
* reasonable names to avoid collisions with other functions.
|
||||||
|
*
|
||||||
|
* -- The second section is specific to Argon2. It encodes and decodes
|
||||||
|
* the parameters, salts and outputs. It does not compute the hash
|
||||||
|
* itself.
|
||||||
|
*
|
||||||
|
* The code was originally written by Thomas Pornin <pornin@bolet.org>,
|
||||||
|
* to whom comments and remarks may be sent. It is released under what
|
||||||
|
* should amount to Public Domain or its closest equivalent; the
|
||||||
|
* following mantra is supposed to incarnate that fact with all the
|
||||||
|
* proper legal rituals:
|
||||||
|
*
|
||||||
|
* ---------------------------------------------------------------------
|
||||||
|
* This file is provided under the terms of Creative Commons CC0 1.0
|
||||||
|
* Public Domain Dedication. To the extent possible under law, the
|
||||||
|
* author (Thomas Pornin) has waived all copyright and related or
|
||||||
|
* neighboring rights to this file. This work is published from: Canada.
|
||||||
|
* ---------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 Thomas Pornin
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ==================================================================== */
|
||||||
|
/*
|
||||||
|
* Common code; could be shared between different hash functions.
|
||||||
|
*
|
||||||
|
* Note: the Base64 functions below assume that uppercase letters (resp.
|
||||||
|
* lowercase letters) have consecutive numerical codes, that fit on 8
|
||||||
|
* bits. All modern systems use ASCII-compatible charsets, where these
|
||||||
|
* properties are true. If you are stuck with a dinosaur of a system
|
||||||
|
* that still defaults to EBCDIC then you already have much bigger
|
||||||
|
* interoperability issues to deal with.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some macros for constant-time comparisons. These work over values in
|
||||||
|
* the 0..255 range. Returned value is 0x00 on "false", 0xFF on "true".
|
||||||
|
*/
|
||||||
|
#define EQ(x, y) ((((0U - ((unsigned)(x) ^ (unsigned)(y))) >> 8) & 0xFF) ^ 0xFF)
|
||||||
|
#define GT(x, y) ((((unsigned)(y) - (unsigned)(x)) >> 8) & 0xFF)
|
||||||
|
#define GE(x, y) (GT(y, x) ^ 0xFF)
|
||||||
|
#define LT(x, y) GT(y, x)
|
||||||
|
#define LE(x, y) GE(y, x)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert value x (0..63) to corresponding Base64 character.
|
||||||
|
*/
|
||||||
|
static int b64_byte_to_char(unsigned x) {
|
||||||
|
return (LT(x, 26) & (x + 'A')) |
|
||||||
|
(GE(x, 26) & LT(x, 52) & (x + ('a' - 26))) |
|
||||||
|
(GE(x, 52) & LT(x, 62) & (x + ('0' - 52))) | (EQ(x, 62) & '+') |
|
||||||
|
(EQ(x, 63) & '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert character c to the corresponding 6-bit value. If character c
|
||||||
|
* is not a Base64 character, then 0xFF (255) is returned.
|
||||||
|
*/
|
||||||
|
static unsigned b64_char_to_byte(int c) {
|
||||||
|
unsigned x;
|
||||||
|
|
||||||
|
x = (GE(c, 'A') & LE(c, 'Z') & (c - 'A')) |
|
||||||
|
(GE(c, 'a') & LE(c, 'z') & (c - ('a' - 26))) |
|
||||||
|
(GE(c, '0') & LE(c, '9') & (c - ('0' - 52))) | (EQ(c, '+') & 62) |
|
||||||
|
(EQ(c, '/') & 63);
|
||||||
|
return x | (EQ(x, 0) & (EQ(c, 'A') ^ 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert some bytes to Base64. 'dst_len' is the length (in characters)
|
||||||
|
* of the output buffer 'dst'; if that buffer is not large enough to
|
||||||
|
* receive the result (including the terminating 0), then (size_t)-1
|
||||||
|
* is returned. Otherwise, the zero-terminated Base64 string is written
|
||||||
|
* in the buffer, and the output length (counted WITHOUT the terminating
|
||||||
|
* zero) is returned.
|
||||||
|
*/
|
||||||
|
static size_t to_base64(char *dst, size_t dst_len, const void *src,
|
||||||
|
size_t src_len) {
|
||||||
|
size_t olen;
|
||||||
|
const unsigned char *buf;
|
||||||
|
unsigned acc, acc_len;
|
||||||
|
|
||||||
|
olen = (src_len / 3) << 2;
|
||||||
|
switch (src_len % 3) {
|
||||||
|
case 2:
|
||||||
|
olen++;
|
||||||
|
/* fall through */
|
||||||
|
case 1:
|
||||||
|
olen += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (dst_len <= olen) {
|
||||||
|
return (size_t)-1;
|
||||||
|
}
|
||||||
|
acc = 0;
|
||||||
|
acc_len = 0;
|
||||||
|
buf = (const unsigned char *)src;
|
||||||
|
while (src_len-- > 0) {
|
||||||
|
acc = (acc << 8) + (*buf++);
|
||||||
|
acc_len += 8;
|
||||||
|
while (acc_len >= 6) {
|
||||||
|
acc_len -= 6;
|
||||||
|
*dst++ = (char)b64_byte_to_char((acc >> acc_len) & 0x3F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (acc_len > 0) {
|
||||||
|
*dst++ = (char)b64_byte_to_char((acc << (6 - acc_len)) & 0x3F);
|
||||||
|
}
|
||||||
|
*dst++ = 0;
|
||||||
|
return olen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decode Base64 chars into bytes. The '*dst_len' value must initially
|
||||||
|
* contain the length of the output buffer '*dst'; when the decoding
|
||||||
|
* ends, the actual number of decoded bytes is written back in
|
||||||
|
* '*dst_len'.
|
||||||
|
*
|
||||||
|
* Decoding stops when a non-Base64 character is encountered, or when
|
||||||
|
* the output buffer capacity is exceeded. If an error occurred (output
|
||||||
|
* buffer is too small, invalid last characters leading to unprocessed
|
||||||
|
* buffered bits), then NULL is returned; otherwise, the returned value
|
||||||
|
* points to the first non-Base64 character in the source stream, which
|
||||||
|
* may be the terminating zero.
|
||||||
|
*/
|
||||||
|
static const char *from_base64(void *dst, size_t *dst_len, const char *src) {
|
||||||
|
size_t len;
|
||||||
|
unsigned char *buf;
|
||||||
|
unsigned acc, acc_len;
|
||||||
|
|
||||||
|
buf = (unsigned char *)dst;
|
||||||
|
len = 0;
|
||||||
|
acc = 0;
|
||||||
|
acc_len = 0;
|
||||||
|
for (;;) {
|
||||||
|
unsigned d;
|
||||||
|
|
||||||
|
d = b64_char_to_byte(*src);
|
||||||
|
if (d == 0xFF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
src++;
|
||||||
|
acc = (acc << 6) + d;
|
||||||
|
acc_len += 6;
|
||||||
|
if (acc_len >= 8) {
|
||||||
|
acc_len -= 8;
|
||||||
|
if ((len++) >= *dst_len) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*buf++ = (acc >> acc_len) & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the input length is equal to 1 modulo 4 (which is
|
||||||
|
* invalid), then there will remain 6 unprocessed bits;
|
||||||
|
* otherwise, only 0, 2 or 4 bits are buffered. The buffered
|
||||||
|
* bits must also all be zero.
|
||||||
|
*/
|
||||||
|
if (acc_len > 4 || (acc & (((unsigned)1 << acc_len) - 1)) != 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*dst_len = len;
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decode decimal integer from 'str'; the value is written in '*v'.
|
||||||
|
* Returned value is a pointer to the next non-decimal character in the
|
||||||
|
* string. If there is no digit at all, or the value encoding is not
|
||||||
|
* minimal (extra leading zeros), or the value does not fit in an
|
||||||
|
* 'unsigned long', then NULL is returned.
|
||||||
|
*/
|
||||||
|
static const char *decode_decimal(const char *str, unsigned long *v) {
|
||||||
|
const char *orig;
|
||||||
|
unsigned long acc;
|
||||||
|
|
||||||
|
acc = 0;
|
||||||
|
for (orig = str;; str++) {
|
||||||
|
int c;
|
||||||
|
|
||||||
|
c = *str;
|
||||||
|
if (c < '0' || c > '9') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c -= '0';
|
||||||
|
if (acc > (ULONG_MAX / 10)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
acc *= 10;
|
||||||
|
if ((unsigned long)c > (ULONG_MAX - acc)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
acc += (unsigned long)c;
|
||||||
|
}
|
||||||
|
if (str == orig || (*orig == '0' && str != (orig + 1))) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*v = acc;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================================================================== */
|
||||||
|
/*
|
||||||
|
* Code specific to Argon2.
|
||||||
|
*
|
||||||
|
* The code below applies the following format:
|
||||||
|
*
|
||||||
|
* $argon2<T>[$v=<num>]$m=<num>,t=<num>,p=<num>$<bin>$<bin>
|
||||||
|
*
|
||||||
|
* where <T> is either 'd', 'id', or 'i', <num> is a decimal integer (positive,
|
||||||
|
* fits in an 'unsigned long'), and <bin> is Base64-encoded data (no '=' padding
|
||||||
|
* characters, no newline or whitespace).
|
||||||
|
*
|
||||||
|
* The last two binary chunks (encoded in Base64) are, in that order,
|
||||||
|
* the salt and the output. Both are required. The binary salt length and the
|
||||||
|
* output length must be in the allowed ranges defined in argon2.h.
|
||||||
|
*
|
||||||
|
* The ctx struct must contain buffers large enough to hold the salt and pwd
|
||||||
|
* when it is fed into decode_string.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int decode_string(argon2_context *ctx, const char *str, argon2_type type) {
|
||||||
|
|
||||||
|
/* check for prefix */
|
||||||
|
#define CC(prefix) \
|
||||||
|
do { \
|
||||||
|
size_t cc_len = strlen(prefix); \
|
||||||
|
if (strncmp(str, prefix, cc_len) != 0) { \
|
||||||
|
return ARGON2_DECODING_FAIL; \
|
||||||
|
} \
|
||||||
|
str += cc_len; \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
/* optional prefix checking with supplied code */
|
||||||
|
#define CC_opt(prefix, code) \
|
||||||
|
do { \
|
||||||
|
size_t cc_len = strlen(prefix); \
|
||||||
|
if (strncmp(str, prefix, cc_len) == 0) { \
|
||||||
|
str += cc_len; \
|
||||||
|
{ code; } \
|
||||||
|
} \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
/* Decoding prefix into decimal */
|
||||||
|
#define DECIMAL(x) \
|
||||||
|
do { \
|
||||||
|
unsigned long dec_x; \
|
||||||
|
str = decode_decimal(str, &dec_x); \
|
||||||
|
if (str == NULL) { \
|
||||||
|
return ARGON2_DECODING_FAIL; \
|
||||||
|
} \
|
||||||
|
(x) = dec_x; \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
/* Decoding prefix into uint32_t decimal */
|
||||||
|
#define DECIMAL_U32(x) \
|
||||||
|
do { \
|
||||||
|
unsigned long dec_x; \
|
||||||
|
str = decode_decimal(str, &dec_x); \
|
||||||
|
if (str == NULL || dec_x > UINT32_MAX) { \
|
||||||
|
return ARGON2_DECODING_FAIL; \
|
||||||
|
} \
|
||||||
|
(x) = (uint32_t)dec_x; \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
/* Decoding base64 into a binary buffer */
|
||||||
|
#define BIN(buf, max_len, len) \
|
||||||
|
do { \
|
||||||
|
size_t bin_len = (max_len); \
|
||||||
|
str = from_base64(buf, &bin_len, str); \
|
||||||
|
if (str == NULL || bin_len > UINT32_MAX) { \
|
||||||
|
return ARGON2_DECODING_FAIL; \
|
||||||
|
} \
|
||||||
|
(len) = (uint32_t)bin_len; \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
size_t maxsaltlen = ctx->saltlen;
|
||||||
|
size_t maxoutlen = ctx->outlen;
|
||||||
|
int validation_result;
|
||||||
|
const char* type_string;
|
||||||
|
|
||||||
|
/* We should start with the argon2_type we are using */
|
||||||
|
type_string = argon2_type2string(type, 0);
|
||||||
|
if (!type_string) {
|
||||||
|
return ARGON2_INCORRECT_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
CC("$");
|
||||||
|
CC(type_string);
|
||||||
|
|
||||||
|
/* Reading the version number if the default is suppressed */
|
||||||
|
ctx->version = ARGON2_VERSION_10;
|
||||||
|
CC_opt("$v=", DECIMAL_U32(ctx->version));
|
||||||
|
|
||||||
|
CC("$m=");
|
||||||
|
DECIMAL_U32(ctx->m_cost);
|
||||||
|
CC(",t=");
|
||||||
|
DECIMAL_U32(ctx->t_cost);
|
||||||
|
CC(",p=");
|
||||||
|
DECIMAL_U32(ctx->lanes);
|
||||||
|
ctx->threads = ctx->lanes;
|
||||||
|
|
||||||
|
CC("$");
|
||||||
|
BIN(ctx->salt, maxsaltlen, ctx->saltlen);
|
||||||
|
CC("$");
|
||||||
|
BIN(ctx->out, maxoutlen, ctx->outlen);
|
||||||
|
|
||||||
|
/* The rest of the fields get the default values */
|
||||||
|
ctx->secret = NULL;
|
||||||
|
ctx->secretlen = 0;
|
||||||
|
ctx->ad = NULL;
|
||||||
|
ctx->adlen = 0;
|
||||||
|
ctx->allocate_cbk = NULL;
|
||||||
|
ctx->free_cbk = NULL;
|
||||||
|
ctx->flags = ARGON2_DEFAULT_FLAGS;
|
||||||
|
|
||||||
|
/* On return, must have valid context */
|
||||||
|
validation_result = validate_inputs(ctx);
|
||||||
|
if (validation_result != ARGON2_OK) {
|
||||||
|
return validation_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can't have any additional characters */
|
||||||
|
if (*str == 0) {
|
||||||
|
return ARGON2_OK;
|
||||||
|
} else {
|
||||||
|
return ARGON2_DECODING_FAIL;
|
||||||
|
}
|
||||||
|
#undef CC
|
||||||
|
#undef CC_opt
|
||||||
|
#undef DECIMAL
|
||||||
|
#undef BIN
|
||||||
|
}
|
||||||
|
|
||||||
|
int encode_string(char *dst, size_t dst_len, argon2_context *ctx,
|
||||||
|
argon2_type type) {
|
||||||
|
#define SS(str) \
|
||||||
|
do { \
|
||||||
|
size_t pp_len = strlen(str); \
|
||||||
|
if (pp_len >= dst_len) { \
|
||||||
|
return ARGON2_ENCODING_FAIL; \
|
||||||
|
} \
|
||||||
|
memcpy(dst, str, pp_len + 1); \
|
||||||
|
dst += pp_len; \
|
||||||
|
dst_len -= pp_len; \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define SX(x) \
|
||||||
|
do { \
|
||||||
|
char tmp[30]; \
|
||||||
|
sprintf(tmp, "%lu", (unsigned long)(x)); \
|
||||||
|
SS(tmp); \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
#define SB(buf, len) \
|
||||||
|
do { \
|
||||||
|
size_t sb_len = to_base64(dst, dst_len, buf, len); \
|
||||||
|
if (sb_len == (size_t)-1) { \
|
||||||
|
return ARGON2_ENCODING_FAIL; \
|
||||||
|
} \
|
||||||
|
dst += sb_len; \
|
||||||
|
dst_len -= sb_len; \
|
||||||
|
} while ((void)0, 0)
|
||||||
|
|
||||||
|
const char* type_string = argon2_type2string(type, 0);
|
||||||
|
int validation_result = validate_inputs(ctx);
|
||||||
|
|
||||||
|
if (!type_string) {
|
||||||
|
return ARGON2_ENCODING_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validation_result != ARGON2_OK) {
|
||||||
|
return validation_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SS("$");
|
||||||
|
SS(type_string);
|
||||||
|
|
||||||
|
SS("$v=");
|
||||||
|
SX(ctx->version);
|
||||||
|
|
||||||
|
SS("$m=");
|
||||||
|
SX(ctx->m_cost);
|
||||||
|
SS(",t=");
|
||||||
|
SX(ctx->t_cost);
|
||||||
|
SS(",p=");
|
||||||
|
SX(ctx->lanes);
|
||||||
|
|
||||||
|
SS("$");
|
||||||
|
SB(ctx->salt, ctx->saltlen);
|
||||||
|
|
||||||
|
SS("$");
|
||||||
|
SB(ctx->out, ctx->outlen);
|
||||||
|
return ARGON2_OK;
|
||||||
|
|
||||||
|
#undef SS
|
||||||
|
#undef SX
|
||||||
|
#undef SB
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t b64len(uint32_t len) {
|
||||||
|
size_t olen = ((size_t)len / 3) << 2;
|
||||||
|
|
||||||
|
switch (len % 3) {
|
||||||
|
case 2:
|
||||||
|
olen++;
|
||||||
|
/* fall through */
|
||||||
|
case 1:
|
||||||
|
olen += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return olen;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t numlen(uint32_t num) {
|
||||||
|
size_t len = 1;
|
||||||
|
while (num >= 10) {
|
||||||
|
++len;
|
||||||
|
num = num / 10;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
194
android/app/src/main/jni/src/ref.c
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "argon2.h"
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include "blake2/blamka-round-ref.h"
|
||||||
|
#include "blake2/blake2-impl.h"
|
||||||
|
#include "blake2/blake2.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function fills a new memory block and optionally XORs the old block over the new one.
|
||||||
|
* @next_block must be initialized.
|
||||||
|
* @param prev_block Pointer to the previous block
|
||||||
|
* @param ref_block Pointer to the reference block
|
||||||
|
* @param next_block Pointer to the block to be constructed
|
||||||
|
* @param with_xor Whether to XOR into the new block (1) or just overwrite (0)
|
||||||
|
* @pre all block pointers must be valid
|
||||||
|
*/
|
||||||
|
static void fill_block(const block *prev_block, const block *ref_block,
|
||||||
|
block *next_block, int with_xor) {
|
||||||
|
block blockR, block_tmp;
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
copy_block(&blockR, ref_block);
|
||||||
|
xor_block(&blockR, prev_block);
|
||||||
|
copy_block(&block_tmp, &blockR);
|
||||||
|
/* Now blockR = ref_block + prev_block and block_tmp = ref_block + prev_block */
|
||||||
|
if (with_xor) {
|
||||||
|
/* Saving the next block contents for XOR over: */
|
||||||
|
xor_block(&block_tmp, next_block);
|
||||||
|
/* Now blockR = ref_block + prev_block and
|
||||||
|
block_tmp = ref_block + prev_block + next_block */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply Blake2 on columns of 64-bit words: (0,1,...,15) , then
|
||||||
|
(16,17,..31)... finally (112,113,...127) */
|
||||||
|
for (i = 0; i < 8; ++i) {
|
||||||
|
BLAKE2_ROUND_NOMSG(
|
||||||
|
blockR.v[16 * i], blockR.v[16 * i + 1], blockR.v[16 * i + 2],
|
||||||
|
blockR.v[16 * i + 3], blockR.v[16 * i + 4], blockR.v[16 * i + 5],
|
||||||
|
blockR.v[16 * i + 6], blockR.v[16 * i + 7], blockR.v[16 * i + 8],
|
||||||
|
blockR.v[16 * i + 9], blockR.v[16 * i + 10], blockR.v[16 * i + 11],
|
||||||
|
blockR.v[16 * i + 12], blockR.v[16 * i + 13], blockR.v[16 * i + 14],
|
||||||
|
blockR.v[16 * i + 15]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply Blake2 on rows of 64-bit words: (0,1,16,17,...112,113), then
|
||||||
|
(2,3,18,19,...,114,115).. finally (14,15,30,31,...,126,127) */
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
BLAKE2_ROUND_NOMSG(
|
||||||
|
blockR.v[2 * i], blockR.v[2 * i + 1], blockR.v[2 * i + 16],
|
||||||
|
blockR.v[2 * i + 17], blockR.v[2 * i + 32], blockR.v[2 * i + 33],
|
||||||
|
blockR.v[2 * i + 48], blockR.v[2 * i + 49], blockR.v[2 * i + 64],
|
||||||
|
blockR.v[2 * i + 65], blockR.v[2 * i + 80], blockR.v[2 * i + 81],
|
||||||
|
blockR.v[2 * i + 96], blockR.v[2 * i + 97], blockR.v[2 * i + 112],
|
||||||
|
blockR.v[2 * i + 113]);
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_block(next_block, &block_tmp);
|
||||||
|
xor_block(next_block, &blockR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void next_addresses(block *address_block, block *input_block,
|
||||||
|
const block *zero_block) {
|
||||||
|
input_block->v[6]++;
|
||||||
|
fill_block(zero_block, input_block, address_block, 0);
|
||||||
|
fill_block(zero_block, address_block, address_block, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fill_segment(const argon2_instance_t *instance,
|
||||||
|
argon2_position_t position) {
|
||||||
|
block *ref_block = NULL, *curr_block = NULL;
|
||||||
|
block address_block, input_block, zero_block;
|
||||||
|
uint64_t pseudo_rand, ref_index, ref_lane;
|
||||||
|
uint32_t prev_offset, curr_offset;
|
||||||
|
uint32_t starting_index;
|
||||||
|
uint32_t i;
|
||||||
|
int data_independent_addressing;
|
||||||
|
|
||||||
|
if (instance == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_independent_addressing =
|
||||||
|
(instance->type == Argon2_i) ||
|
||||||
|
(instance->type == Argon2_id && (position.pass == 0) &&
|
||||||
|
(position.slice < ARGON2_SYNC_POINTS / 2));
|
||||||
|
|
||||||
|
if (data_independent_addressing) {
|
||||||
|
init_block_value(&zero_block, 0);
|
||||||
|
init_block_value(&input_block, 0);
|
||||||
|
|
||||||
|
input_block.v[0] = position.pass;
|
||||||
|
input_block.v[1] = position.lane;
|
||||||
|
input_block.v[2] = position.slice;
|
||||||
|
input_block.v[3] = instance->memory_blocks;
|
||||||
|
input_block.v[4] = instance->passes;
|
||||||
|
input_block.v[5] = instance->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
starting_index = 0;
|
||||||
|
|
||||||
|
if ((0 == position.pass) && (0 == position.slice)) {
|
||||||
|
starting_index = 2; /* we have already generated the first two blocks */
|
||||||
|
|
||||||
|
/* Don't forget to generate the first block of addresses: */
|
||||||
|
if (data_independent_addressing) {
|
||||||
|
next_addresses(&address_block, &input_block, &zero_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Offset of the current block */
|
||||||
|
curr_offset = position.lane * instance->lane_length +
|
||||||
|
position.slice * instance->segment_length + starting_index;
|
||||||
|
|
||||||
|
if (0 == curr_offset % instance->lane_length) {
|
||||||
|
/* Last block in this lane */
|
||||||
|
prev_offset = curr_offset + instance->lane_length - 1;
|
||||||
|
} else {
|
||||||
|
/* Previous block */
|
||||||
|
prev_offset = curr_offset - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = starting_index; i < instance->segment_length;
|
||||||
|
++i, ++curr_offset, ++prev_offset) {
|
||||||
|
/*1.1 Rotating prev_offset if needed */
|
||||||
|
if (curr_offset % instance->lane_length == 1) {
|
||||||
|
prev_offset = curr_offset - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1.2 Computing the index of the reference block */
|
||||||
|
/* 1.2.1 Taking pseudo-random value from the previous block */
|
||||||
|
if (data_independent_addressing) {
|
||||||
|
if (i % ARGON2_ADDRESSES_IN_BLOCK == 0) {
|
||||||
|
next_addresses(&address_block, &input_block, &zero_block);
|
||||||
|
}
|
||||||
|
pseudo_rand = address_block.v[i % ARGON2_ADDRESSES_IN_BLOCK];
|
||||||
|
} else {
|
||||||
|
pseudo_rand = instance->memory[prev_offset].v[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1.2.2 Computing the lane of the reference block */
|
||||||
|
ref_lane = ((pseudo_rand >> 32)) % instance->lanes;
|
||||||
|
|
||||||
|
if ((position.pass == 0) && (position.slice == 0)) {
|
||||||
|
/* Can not reference other lanes yet */
|
||||||
|
ref_lane = position.lane;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1.2.3 Computing the number of possible reference block within the
|
||||||
|
* lane.
|
||||||
|
*/
|
||||||
|
position.index = i;
|
||||||
|
ref_index = index_alpha(instance, &position, pseudo_rand & 0xFFFFFFFF,
|
||||||
|
ref_lane == position.lane);
|
||||||
|
|
||||||
|
/* 2 Creating a new block */
|
||||||
|
ref_block =
|
||||||
|
instance->memory + instance->lane_length * ref_lane + ref_index;
|
||||||
|
curr_block = instance->memory + curr_offset;
|
||||||
|
if (ARGON2_VERSION_10 == instance->version) {
|
||||||
|
/* version 1.2.1 and earlier: overwrite, not XOR */
|
||||||
|
fill_block(instance->memory + prev_offset, ref_block, curr_block, 0);
|
||||||
|
} else {
|
||||||
|
if(0 == position.pass) {
|
||||||
|
fill_block(instance->memory + prev_offset, ref_block,
|
||||||
|
curr_block, 0);
|
||||||
|
} else {
|
||||||
|
fill_block(instance->memory + prev_offset, ref_block,
|
||||||
|
curr_block, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
android/app/src/main/jni/src/thread.c
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if !defined(ARGON2_NO_THREADS)
|
||||||
|
|
||||||
|
#include "thread.h"
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int argon2_thread_create(argon2_thread_handle_t *handle,
|
||||||
|
argon2_thread_func_t func, void *args) {
|
||||||
|
if (NULL == handle || func == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#if defined(_WIN32)
|
||||||
|
*handle = _beginthreadex(NULL, 0, func, args, 0, NULL);
|
||||||
|
return *handle != 0 ? 0 : -1;
|
||||||
|
#else
|
||||||
|
return pthread_create(handle, NULL, func, args);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int argon2_thread_join(argon2_thread_handle_t handle) {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if (WaitForSingleObject((HANDLE)handle, INFINITE) == WAIT_OBJECT_0) {
|
||||||
|
return CloseHandle((HANDLE)handle) != 0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
#else
|
||||||
|
return pthread_join(handle, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void argon2_thread_exit(void) {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
_endthreadex(0);
|
||||||
|
#else
|
||||||
|
pthread_exit(NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ARGON2_NO_THREADS */
|
||||||
67
android/app/src/main/jni/thread.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Argon2 reference source code package - reference C implementations
|
||||||
|
*
|
||||||
|
* Copyright 2015
|
||||||
|
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
|
||||||
|
*
|
||||||
|
* You may use this work under the terms of a Creative Commons CC0 1.0
|
||||||
|
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
|
||||||
|
* these licenses can be found at:
|
||||||
|
*
|
||||||
|
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* You should have received a copy of both of these licenses along with this
|
||||||
|
* software. If not, they may be obtained at the above URLs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARGON2_THREAD_H
|
||||||
|
#define ARGON2_THREAD_H
|
||||||
|
|
||||||
|
#if !defined(ARGON2_NO_THREADS)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Here we implement an abstraction layer for the simpĺe requirements
|
||||||
|
of the Argon2 code. We only require 3 primitives---thread creation,
|
||||||
|
joining, and termination---so full emulation of the pthreads API
|
||||||
|
is unwarranted. Currently we wrap pthreads and Win32 threads.
|
||||||
|
|
||||||
|
The API defines 2 types: the function pointer type,
|
||||||
|
argon2_thread_func_t,
|
||||||
|
and the type of the thread handle---argon2_thread_handle_t.
|
||||||
|
*/
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <process.h>
|
||||||
|
typedef unsigned(__stdcall *argon2_thread_func_t)(void *);
|
||||||
|
typedef uintptr_t argon2_thread_handle_t;
|
||||||
|
#else
|
||||||
|
#include <pthread.h>
|
||||||
|
typedef void *(*argon2_thread_func_t)(void *);
|
||||||
|
typedef pthread_t argon2_thread_handle_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Creates a thread
|
||||||
|
* @param handle pointer to a thread handle, which is the output of this
|
||||||
|
* function. Must not be NULL.
|
||||||
|
* @param func A function pointer for the thread's entry point. Must not be
|
||||||
|
* NULL.
|
||||||
|
* @param args Pointer that is passed as an argument to @func. May be NULL.
|
||||||
|
* @return 0 if @handle and @func are valid pointers and a thread is successfully
|
||||||
|
* created.
|
||||||
|
*/
|
||||||
|
int argon2_thread_create(argon2_thread_handle_t *handle,
|
||||||
|
argon2_thread_func_t func, void *args);
|
||||||
|
|
||||||
|
/* Waits for a thread to terminate
|
||||||
|
* @param handle Handle to a thread created with argon2_thread_create.
|
||||||
|
* @return 0 if @handle is a valid handle, and joining completed successfully.
|
||||||
|
*/
|
||||||
|
int argon2_thread_join(argon2_thread_handle_t handle);
|
||||||
|
|
||||||
|
/* Terminate the current thread. Must be run inside a thread created by
|
||||||
|
* argon2_thread_create.
|
||||||
|
*/
|
||||||
|
void argon2_thread_exit(void);
|
||||||
|
|
||||||
|
#endif /* ARGON2_NO_THREADS */
|
||||||
|
#endif
|
||||||
BIN
android/app/src/main/jniLibs/arm64-v8a/libargon2.so
Normal file
BIN
android/app/src/main/jniLibs/armeabi-v7a/libargon2.so
Normal file
BIN
android/app/src/main/jniLibs/x86/libargon2.so
Normal file
BIN
android/app/src/main/jniLibs/x86_64/libargon2.so
Normal file
@@ -1,9 +1,3 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<!-- The INTERNET permission is required for development. Specifically,
|
|
||||||
the Flutter tool needs it to communicate with the running application
|
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
|
||||||
-->
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
0
flutter_01.png
Normal file
BIN
flutter_02.png
Normal file
|
After Width: | Height: | Size: 364 KiB |
BIN
flutter_03.png
Normal file
|
After Width: | Height: | Size: 365 KiB |
BIN
flutter_04.png
Normal file
|
After Width: | Height: | Size: 365 KiB |
BIN
lib/assets/fonts/montserrat_bold.ttf
Normal file
BIN
lib/assets/fonts/montserrat_regular.ttf
Normal file
BIN
lib/assets/images/activity_placeholder.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
lib/assets/images/event_placeholder.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
lib/assets/images/friend_placeholder.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
lib/assets/images/group_placeholder.png
Normal file
|
After Width: | Height: | Size: 436 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.5 MiB |
BIN
lib/assets/images/logoaw.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
lib/assets/images/logolionsdev.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
lib/assets/images/story_placeholder.png
Normal file
|
After Width: | Height: | Size: 313 KiB |
BIN
lib/assets/images/user_placeholder.png
Normal file
|
After Width: | Height: | Size: 222 KiB |
BIN
lib/assets/videos/test.mp4
Normal file
@@ -5,18 +5,31 @@ import '../../data/datasources/user_remote_data_source.dart';
|
|||||||
import '../../data/repositories/user_repository_impl.dart';
|
import '../../data/repositories/user_repository_impl.dart';
|
||||||
import '../../domain/usecases/get_user.dart';
|
import '../../domain/usecases/get_user.dart';
|
||||||
|
|
||||||
|
/// Instance globale pour gérer l'injection des dépendances via GetIt
|
||||||
final sl = GetIt.instance;
|
final sl = GetIt.instance;
|
||||||
|
|
||||||
|
/// Fonction d'initialisation pour enregistrer toutes les dépendances.
|
||||||
|
/// Utilisée pour fournir des services, des data sources, des repositories et des use cases.
|
||||||
void init() {
|
void init() {
|
||||||
|
// Log de démarrage de l'injection des dépendances
|
||||||
|
print("Démarrage de l'initialisation des dépendances.");
|
||||||
|
|
||||||
// Register Http Client
|
// Register Http Client
|
||||||
sl.registerLazySingleton(() => http.Client());
|
sl.registerLazySingleton(() => http.Client());
|
||||||
|
print("Client HTTP enregistré.");
|
||||||
|
|
||||||
// Register Data Sources
|
// Register Data Sources
|
||||||
sl.registerLazySingleton(() => UserRemoteDataSource(sl()));
|
sl.registerLazySingleton(() => UserRemoteDataSource(sl()));
|
||||||
|
print("DataSource pour UserRemoteDataSource enregistré.");
|
||||||
|
|
||||||
// Register Repositories
|
// Register Repositories
|
||||||
sl.registerLazySingleton(() => UserRepositoryImpl(remoteDataSource: sl()));
|
sl.registerLazySingleton(() => UserRepositoryImpl(remoteDataSource: sl()));
|
||||||
|
print("Repository pour UserRepositoryImpl enregistré.");
|
||||||
|
|
||||||
// Register Use Cases
|
// Register Use Cases
|
||||||
sl.registerLazySingleton(() => GetUser(sl()));
|
sl.registerLazySingleton(() => GetUser(sl()));
|
||||||
|
print("UseCase pour GetUser enregistré.");
|
||||||
|
|
||||||
|
// Log de fin d'initialisation des dépendances
|
||||||
|
print("Initialisation des dépendances terminée.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,39 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:afterwork/presentation/screens/login/login_screen.dart';
|
import 'package:afterwork/presentation/screens/login/login_screen.dart';
|
||||||
import 'package:afterwork/presentation/screens/home/home_screen.dart';
|
|
||||||
import 'package:afterwork/presentation/screens/event/event_screen.dart';
|
import 'package:afterwork/presentation/screens/event/event_screen.dart';
|
||||||
import 'package:afterwork/presentation/screens/story/story_screen.dart';
|
import 'package:afterwork/presentation/screens/story/story_screen.dart';
|
||||||
import 'package:afterwork/presentation/screens/profile/profile_screen.dart';
|
import 'package:afterwork/presentation/screens/profile/profile_screen.dart';
|
||||||
import 'package:afterwork/presentation/screens/settings/settings_screen.dart';
|
import 'package:afterwork/presentation/screens/settings/settings_screen.dart';
|
||||||
|
import 'package:afterwork/presentation/screens/home/home_screen.dart';
|
||||||
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
||||||
import '../presentation/reservations/reservations_screen.dart';
|
import '../presentation/reservations/reservations_screen.dart';
|
||||||
|
|
||||||
|
/// Router personnalisé pour gérer la navigation dans l'application.
|
||||||
|
/// Les logs permettent de tracer chaque navigation dans la console.
|
||||||
class AppRouter {
|
class AppRouter {
|
||||||
final EventRemoteDataSource eventRemoteDataSource;
|
final EventRemoteDataSource eventRemoteDataSource;
|
||||||
final String userId;
|
final String userId;
|
||||||
final String userName;
|
final String userName;
|
||||||
final String userLastName;
|
final String userLastName;
|
||||||
|
|
||||||
|
/// Initialisation des informations utilisateur et source de données
|
||||||
AppRouter({
|
AppRouter({
|
||||||
required this.eventRemoteDataSource,
|
required this.eventRemoteDataSource,
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.userName,
|
required this.userName,
|
||||||
required this.userLastName,
|
required this.userLastName,
|
||||||
});
|
}) {
|
||||||
|
print("AppRouter initialisé avec les infos utilisateur : $userId, $userName, $userLastName");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Génération des routes pour l'application
|
||||||
Route<dynamic> generateRoute(RouteSettings settings) {
|
Route<dynamic> generateRoute(RouteSettings settings) {
|
||||||
|
print("Navigation vers la route : ${settings.name}");
|
||||||
|
|
||||||
switch (settings.name) {
|
switch (settings.name) {
|
||||||
case '/':
|
case '/':
|
||||||
return MaterialPageRoute(builder: (_) => const LoginScreen());
|
return MaterialPageRoute(builder: (_) => const LoginScreen());
|
||||||
|
|
||||||
case '/home':
|
case '/home':
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
builder: (_) => HomeScreen(
|
builder: (_) => HomeScreen(
|
||||||
@@ -32,25 +41,31 @@ class AppRouter {
|
|||||||
userId: userId,
|
userId: userId,
|
||||||
userName: userName,
|
userName: userName,
|
||||||
userLastName: userLastName,
|
userLastName: userLastName,
|
||||||
|
userProfileImage: 'lib/assets/images/profile_picture.png',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
case '/event':
|
case '/event':
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
builder: (_) => EventScreen(
|
builder: (_) => EventScreen(
|
||||||
eventRemoteDataSource: eventRemoteDataSource,
|
|
||||||
userId: userId,
|
userId: userId,
|
||||||
userName: userName,
|
userName: userName,
|
||||||
userLastName: userLastName,
|
userLastName: userLastName,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
case '/story':
|
case '/story':
|
||||||
return MaterialPageRoute(builder: (_) => const StoryScreen());
|
return MaterialPageRoute(builder: (_) => const StoryScreen());
|
||||||
|
|
||||||
case '/profile':
|
case '/profile':
|
||||||
return MaterialPageRoute(builder: (_) => const ProfileScreen());
|
return MaterialPageRoute(builder: (_) => const ProfileScreen());
|
||||||
|
|
||||||
case '/settings':
|
case '/settings':
|
||||||
return MaterialPageRoute(builder: (_) => const SettingsScreen());
|
return MaterialPageRoute(builder: (_) => const SettingsScreen());
|
||||||
|
|
||||||
case '/reservations':
|
case '/reservations':
|
||||||
return MaterialPageRoute(builder: (_) => const ReservationsScreen());
|
return MaterialPageRoute(builder: (_) => const ReservationsScreen());
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
builder: (_) => const Scaffold(
|
builder: (_) => const Scaffold(
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Classe utilitaire pour gérer les couleurs de l'application en mode clair et sombre.
|
||||||
|
class AppColors {
|
||||||
|
// Thème clair
|
||||||
|
static const Color lightPrimary = Color(0xFF0057D9);
|
||||||
|
static const Color lightSecondary = Color(0xFFFFC107);
|
||||||
|
static const Color lightOnPrimary = Colors.white;
|
||||||
|
static const Color lightOnSecondary = Color(0xFF212121);
|
||||||
|
static const Color lightBackground = Colors.white;
|
||||||
|
static const Color lightSurface = Color(0xFFFFFFFF);
|
||||||
|
static const Color lightTextPrimary = Color(0xFF212121);
|
||||||
|
static const Color lightTextSecondary = Color(0xFF616161);
|
||||||
|
static const Color lightCardColor = Color(0xFFFFFFFF);
|
||||||
|
static const Color lightAccentColor = Color(0xFF4CAF50);
|
||||||
|
static const Color lightError = Color(0xFFB00020);
|
||||||
|
static const Color lightIconPrimary = Color(0xFF212121); // Icône primaire sombre
|
||||||
|
static const Color lightIconSecondary = Color(0xFF757575); // Icône secondaire gris clair
|
||||||
|
// Thème sombre
|
||||||
|
static const Color darkPrimary = Color(0xFF121212);
|
||||||
|
static const Color darkSecondary = Color(0xFFFF5722);
|
||||||
|
static const Color darkOnPrimary = Colors.white;
|
||||||
|
static const Color darkOnSecondary = Colors.white;
|
||||||
|
static const Color darkBackground = Color(0xFF121212);
|
||||||
|
static const Color darkSurface = Color(0xFF1F1F1F);
|
||||||
|
static const Color darkTextPrimary = Color(0xFFE0E0E0);
|
||||||
|
static const Color darkTextSecondary = Color(0xFFBDBDBD);
|
||||||
|
static const Color darkCardColor = Color(0xFF2C2C2C);
|
||||||
|
static const Color darkAccentColor = Color(0xFF81C784);
|
||||||
|
static const Color darkError = Color(0xFFCF6679);
|
||||||
|
static const Color darkIconPrimary = Colors.white; // Icône primaire blanche
|
||||||
|
static const Color darkIconSecondary = Color(0xFFBDBDBD); // Icône secondaire gris clair
|
||||||
|
|
||||||
|
// Sélection automatique des couleurs en fonction du mode de thème
|
||||||
|
static Color get primary => isDarkMode() ? darkPrimary : lightPrimary;
|
||||||
|
static Color get secondary => isDarkMode() ? darkSecondary : lightSecondary;
|
||||||
|
static Color get onPrimary => isDarkMode() ? darkOnPrimary : lightOnPrimary;
|
||||||
|
static Color get onSecondary => isDarkMode() ? darkOnSecondary : lightOnSecondary;
|
||||||
|
static Color get backgroundColor => isDarkMode() ? darkBackground : lightBackground;
|
||||||
|
static Color get surface => isDarkMode() ? darkSurface : lightSurface;
|
||||||
|
static Color get textPrimary => isDarkMode() ? darkTextPrimary : lightTextPrimary;
|
||||||
|
static Color get textSecondary => isDarkMode() ? darkTextSecondary : lightTextSecondary;
|
||||||
|
static Color get cardColor => isDarkMode() ? darkCardColor : lightCardColor;
|
||||||
|
static Color get accentColor => isDarkMode() ? darkAccentColor : lightAccentColor;
|
||||||
|
static Color get errorColor => isDarkMode() ? darkError : lightError;
|
||||||
|
static Color get iconPrimary => isDarkMode() ? darkIconPrimary : lightIconPrimary;
|
||||||
|
static Color get iconSecondary => isDarkMode() ? darkIconSecondary : lightIconSecondary;
|
||||||
|
|
||||||
|
/// Méthode utilitaire pour vérifier si le mode sombre est activé.
|
||||||
|
static bool isDarkMode() {
|
||||||
|
final brightness = WidgetsBinding.instance.platformDispatcher.platformBrightness;
|
||||||
|
return brightness == Brightness.dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,31 @@
|
|||||||
class Urls {
|
class Urls {
|
||||||
static const String baseUrl = 'http://192.168.1.145:8085';
|
static const String baseUrl = 'http://192.168.1.145:8085';
|
||||||
// static const String login = baseUrl + 'auth/login';
|
|
||||||
static const String eventsUrl = '$baseUrl/events';
|
// Authentication and Users Endpoints
|
||||||
// Ajoute d'autres URLs ici
|
static const String authenticateUser = '$baseUrl/users/authenticate';
|
||||||
|
static const String createUser = '$baseUrl/users';
|
||||||
|
static const String getUserById = '$baseUrl/users'; // Append '/{id}' dynamically
|
||||||
|
static const String deleteUser = '$baseUrl/users'; // Append '/{id}' dynamically
|
||||||
|
static const String updateUserProfileImage = '$baseUrl/users'; // Append '/{id}/profile-image' dynamically
|
||||||
|
|
||||||
|
// Events Endpoints
|
||||||
|
static const String createEvent = '$baseUrl/events';
|
||||||
|
static const String getEventById = '$baseUrl/events'; // Append '/{id}' dynamically
|
||||||
|
static const String deleteEvent = '$baseUrl/events'; // Append '/{id}' dynamically
|
||||||
|
static const String getEventsAfterDate = '$baseUrl/events/after-date';
|
||||||
|
static const String addParticipant = '$baseUrl/events'; // Append '/{id}/participants' dynamically
|
||||||
|
static const String removeParticipant = '$baseUrl/events'; // Append '/{id}/participants/{userId}' dynamically
|
||||||
|
static const String getNumberOfParticipants = '$baseUrl/events'; // Append '/{id}/participants/count' dynamically
|
||||||
|
static const String closeEvent = '$baseUrl/events'; // Append '/{id}/close' dynamically
|
||||||
|
static const String updateEvent = '$baseUrl/events'; // Append '/{id}' dynamically
|
||||||
|
static const String updateEventImage = '$baseUrl/events'; // Append '/{id}/image' dynamically
|
||||||
|
static const String getAllEvents = '$baseUrl/events';
|
||||||
|
static const String getEventsByCategory = '$baseUrl/events/category'; // Append '/{category}' dynamically
|
||||||
|
static const String updateEventStatus = '$baseUrl/events'; // Append '/{id}/status' dynamically
|
||||||
|
static const String searchEvents = '$baseUrl/events/search'; // Use query parameter for 'keyword'
|
||||||
|
static const String getEventsByUser = '$baseUrl/events/user'; // Append '/{userId}' dynamically
|
||||||
|
static const String getEventsByStatus = '$baseUrl/events/status'; // Append '/{status}' dynamically
|
||||||
|
static const String getEventsBetweenDates = '$baseUrl/events/between-dates'; // Use query parameters for startDate and endDate
|
||||||
|
|
||||||
|
// Other URLs can be added here as the project expands
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,3 +27,26 @@ class ServerExceptionWithMessage implements Exception {
|
|||||||
String toString() => 'ServerException: $message';
|
String toString() => 'ServerException: $message';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UserNotFoundException implements Exception {
|
||||||
|
final String message;
|
||||||
|
UserNotFoundException([this.message = "User not found"]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "UserNotFoundException: $message";
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConflictException implements Exception {
|
||||||
|
final String message;
|
||||||
|
ConflictException([this.message = "Conflict"]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "ConflictException: $message";
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnauthorizedException implements Exception {
|
||||||
|
final String message;
|
||||||
|
UnauthorizedException([this.message = "Unauthorized"]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "UnauthorizedException: $message";
|
||||||
|
}
|
||||||
@@ -1,20 +1,116 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:afterwork/core/constants/colors.dart';
|
||||||
|
|
||||||
|
/// Classe qui définit les thèmes de l'application AfterWork.
|
||||||
|
/// Elle gère à la fois le thème clair et le thème sombre, avec des personnalisations
|
||||||
|
/// pour les couleurs, les boutons, les textes et d'autres éléments visuels.
|
||||||
class AppTheme {
|
class AppTheme {
|
||||||
|
/// Thème clair
|
||||||
static final ThemeData lightTheme = ThemeData(
|
static final ThemeData lightTheme = ThemeData(
|
||||||
primaryColor: Colors.blue,
|
|
||||||
colorScheme: const ColorScheme.light(
|
|
||||||
secondary: Colors.orange,
|
|
||||||
),
|
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
buttonTheme: const ButtonThemeData(buttonColor: Colors.blue),
|
primaryColor: AppColors.lightPrimary,
|
||||||
|
scaffoldBackgroundColor: AppColors.lightBackground,
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
color: AppColors.lightPrimary,
|
||||||
|
iconTheme: IconThemeData(color: AppColors.lightOnPrimary),
|
||||||
|
),
|
||||||
|
iconTheme: const IconThemeData(color: AppColors.lightTextPrimary),
|
||||||
|
colorScheme: const ColorScheme.light(
|
||||||
|
primary: AppColors.lightPrimary,
|
||||||
|
secondary: AppColors.lightSecondary,
|
||||||
|
onPrimary: AppColors.lightOnPrimary,
|
||||||
|
onSecondary: AppColors.lightOnSecondary,
|
||||||
|
surface: AppColors.lightSurface,
|
||||||
|
),
|
||||||
|
buttonTheme: const ButtonThemeData(
|
||||||
|
buttonColor: AppColors.lightPrimary,
|
||||||
|
textTheme: ButtonTextTheme.primary,
|
||||||
|
),
|
||||||
|
textTheme: const TextTheme(
|
||||||
|
bodyLarge: TextStyle(color: AppColors.lightTextPrimary),
|
||||||
|
bodyMedium: TextStyle(color: AppColors.lightTextSecondary),
|
||||||
|
titleLarge: TextStyle(color: AppColors.lightTextPrimary),
|
||||||
|
),
|
||||||
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
filled: true,
|
||||||
|
fillColor: AppColors.lightSurface,
|
||||||
|
labelStyle: const TextStyle(color: AppColors.lightTextPrimary),
|
||||||
|
hintStyle: const TextStyle(color: AppColors.lightTextSecondary),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: const BorderSide(color: AppColors.lightPrimary),
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: const BorderSide(color: Colors.grey),
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButtonTheme: const FloatingActionButtonThemeData(
|
||||||
|
backgroundColor: AppColors.lightPrimary,
|
||||||
|
foregroundColor: AppColors.lightOnPrimary,
|
||||||
|
),
|
||||||
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.lightPrimary,
|
||||||
|
foregroundColor: AppColors.lightOnPrimary,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||||
|
textStyle: const TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Thème sombre
|
||||||
static final ThemeData darkTheme = ThemeData(
|
static final ThemeData darkTheme = ThemeData(
|
||||||
primaryColor: Colors.black,
|
|
||||||
colorScheme: const ColorScheme.dark(
|
|
||||||
secondary: Colors.red,
|
|
||||||
),
|
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
|
primaryColor: AppColors.darkPrimary,
|
||||||
|
scaffoldBackgroundColor: AppColors.darkBackground,
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
color: AppColors.darkPrimary,
|
||||||
|
iconTheme: IconThemeData(color: AppColors.darkOnPrimary),
|
||||||
|
),
|
||||||
|
iconTheme: const IconThemeData(color: AppColors.darkTextPrimary),
|
||||||
|
colorScheme: const ColorScheme.dark(
|
||||||
|
primary: AppColors.darkPrimary,
|
||||||
|
secondary: AppColors.darkSecondary,
|
||||||
|
onPrimary: AppColors.darkOnPrimary,
|
||||||
|
onSecondary: AppColors.darkOnSecondary,
|
||||||
|
surface: AppColors.darkSurface,
|
||||||
|
),
|
||||||
|
buttonTheme: const ButtonThemeData(
|
||||||
|
buttonColor: AppColors.darkSecondary,
|
||||||
|
textTheme: ButtonTextTheme.primary,
|
||||||
|
),
|
||||||
|
textTheme: const TextTheme(
|
||||||
|
bodyLarge: TextStyle(color: AppColors.darkTextPrimary),
|
||||||
|
bodyMedium: TextStyle(color: AppColors.darkTextSecondary),
|
||||||
|
titleLarge: TextStyle(color: AppColors.darkTextPrimary),
|
||||||
|
),
|
||||||
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
filled: true,
|
||||||
|
fillColor: AppColors.darkSurface,
|
||||||
|
labelStyle: const TextStyle(color: AppColors.darkTextPrimary),
|
||||||
|
hintStyle: const TextStyle(color: AppColors.darkTextSecondary),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: const BorderSide(color: AppColors.darkSecondary),
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: const BorderSide(color: AppColors.darkTextSecondary),
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButtonTheme: const FloatingActionButtonThemeData(
|
||||||
|
backgroundColor: AppColors.darkSecondary,
|
||||||
|
foregroundColor: AppColors.darkOnPrimary,
|
||||||
|
),
|
||||||
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.darkSecondary,
|
||||||
|
foregroundColor: AppColors.darkOnPrimary,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||||
|
textStyle: const TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
lib/core/theme/theme_provider.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'app_theme.dart'; // Importe tes définitions de thème
|
||||||
|
|
||||||
|
class ThemeProvider with ChangeNotifier {
|
||||||
|
bool _isDarkMode = false; // Mode sombre par défaut désactivé
|
||||||
|
|
||||||
|
bool get isDarkMode => _isDarkMode;
|
||||||
|
|
||||||
|
void toggleTheme() {
|
||||||
|
_isDarkMode = !_isDarkMode;
|
||||||
|
notifyListeners(); // Notifie les widgets dépendants
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utilise AppTheme pour obtenir le thème courant
|
||||||
|
ThemeData get currentTheme {
|
||||||
|
return _isDarkMode ? AppTheme.darkTheme : AppTheme.lightTheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
lib/core/utils/calculate_time_ago.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Fichier utilitaire pour le calcul du temps écoulé
|
||||||
|
String calculateTimeAgo(DateTime publicationDate) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final difference = now.difference(publicationDate);
|
||||||
|
|
||||||
|
if (difference.inDays > 0) {
|
||||||
|
return '${difference.inDays} jour${difference.inDays > 1 ? 's' : ''}';
|
||||||
|
} else if (difference.inHours > 0) {
|
||||||
|
return '${difference.inHours} heure${difference.inHours > 1 ? 's' : ''}';
|
||||||
|
} else if (difference.inMinutes > 0) {
|
||||||
|
return '${difference.inMinutes} minute${difference.inMinutes > 1 ? 's' : ''}';
|
||||||
|
} else {
|
||||||
|
return 'À l\'instant';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import 'package:intl/intl.dart';
|
|||||||
|
|
||||||
class DateFormatter {
|
class DateFormatter {
|
||||||
static String formatDate(DateTime date) {
|
static String formatDate(DateTime date) {
|
||||||
return DateFormat('EEEE dd MMMM yyyy', 'fr_FR').format(date);
|
// Formater la date avec l'heure incluse
|
||||||
|
return DateFormat('EEEE dd MMMM yyyy, à HH:mm', 'fr_FR').format(date);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class EventRemoteDataSource {
|
|||||||
print('Création d\'un nouvel événement avec les données: ${event.toJson()}');
|
print('Création d\'un nouvel événement avec les données: ${event.toJson()}');
|
||||||
|
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse(Urls.eventsUrl),
|
Uri.parse(Urls.createEvent),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(event.toJson()),
|
body: jsonEncode(event.toJson()),
|
||||||
);
|
);
|
||||||
@@ -53,7 +53,7 @@ class EventRemoteDataSource {
|
|||||||
Future<EventModel> getEventById(String id) async {
|
Future<EventModel> getEventById(String id) async {
|
||||||
print('Récupération de l\'événement avec l\'ID: $id');
|
print('Récupération de l\'événement avec l\'ID: $id');
|
||||||
|
|
||||||
final response = await client.get(Uri.parse('${Urls.eventsUrl}/$id'));
|
final response = await client.get(Uri.parse('${Urls.getEventById}/$id'));
|
||||||
|
|
||||||
print('Statut de la réponse: ${response.statusCode}');
|
print('Statut de la réponse: ${response.statusCode}');
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ class EventRemoteDataSource {
|
|||||||
print('Mise à jour de l\'événement avec l\'ID: $id, données: ${event.toJson()}');
|
print('Mise à jour de l\'événement avec l\'ID: $id, données: ${event.toJson()}');
|
||||||
|
|
||||||
final response = await client.put(
|
final response = await client.put(
|
||||||
Uri.parse('${Urls.eventsUrl}/$id'),
|
Uri.parse('${Urls.updateEvent}/$id'),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(event.toJson()),
|
body: jsonEncode(event.toJson()),
|
||||||
);
|
);
|
||||||
@@ -91,7 +91,7 @@ class EventRemoteDataSource {
|
|||||||
Future<void> deleteEvent(String id) async {
|
Future<void> deleteEvent(String id) async {
|
||||||
print('Suppression de l\'événement avec l\'ID: $id');
|
print('Suppression de l\'événement avec l\'ID: $id');
|
||||||
|
|
||||||
final response = await client.delete(Uri.parse('${Urls.eventsUrl}/$id'));
|
final response = await client.delete(Uri.parse('${Urls.deleteEvent}/$id'));
|
||||||
|
|
||||||
print('Statut de la réponse: ${response.statusCode}');
|
print('Statut de la réponse: ${response.statusCode}');
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ class EventRemoteDataSource {
|
|||||||
print('Participation à l\'événement avec l\'ID: $eventId, utilisateur: $userId');
|
print('Participation à l\'événement avec l\'ID: $eventId, utilisateur: $userId');
|
||||||
|
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse('${Urls.eventsUrl}/$eventId/participate'),
|
Uri.parse('${Urls.addParticipant}/$eventId/participate'),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode({'userId': userId}),
|
body: jsonEncode({'userId': userId}),
|
||||||
);
|
);
|
||||||
@@ -129,7 +129,7 @@ class EventRemoteDataSource {
|
|||||||
print('Réaction à l\'événement avec l\'ID: $eventId, utilisateur: $userId');
|
print('Réaction à l\'événement avec l\'ID: $eventId, utilisateur: $userId');
|
||||||
|
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse('${Urls.eventsUrl}/$eventId/react'),
|
Uri.parse('${Urls.baseUrl}/$eventId/react'),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode({'userId': userId}),
|
body: jsonEncode({'userId': userId}),
|
||||||
);
|
);
|
||||||
@@ -149,7 +149,7 @@ class EventRemoteDataSource {
|
|||||||
print('Fermeture de l\'événement avec l\'ID: $eventId');
|
print('Fermeture de l\'événement avec l\'ID: $eventId');
|
||||||
|
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse('${Urls.eventsUrl}/$eventId/close'),
|
Uri.parse('${Urls.closeEvent}/$eventId/close'),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ class EventRemoteDataSource {
|
|||||||
print('Réouverture de l\'événement avec l\'ID: $eventId');
|
print('Réouverture de l\'événement avec l\'ID: $eventId');
|
||||||
|
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse('${Urls.eventsUrl}/$eventId/reopen'),
|
Uri.parse('${Urls.baseUrl}/$eventId/reopen'),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -193,4 +193,5 @@ class EventRemoteDataSource {
|
|||||||
throw ServerExceptionWithMessage('Une erreur est survenue lors de la réouverture de l\'événement.');
|
throw ServerExceptionWithMessage('Une erreur est survenue lors de la réouverture de l\'événement.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +1,141 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:afterwork/core/constants/urls.dart';
|
import 'package:afterwork/core/constants/urls.dart';
|
||||||
import 'package:afterwork/data/models/user_model.dart';
|
import 'package:afterwork/data/models/user_model.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import '../../core/errors/exceptions.dart';
|
import '../../core/errors/exceptions.dart';
|
||||||
|
|
||||||
|
/// Classe pour gérer les opérations API pour les utilisateurs.
|
||||||
|
/// Chaque action est loguée pour faciliter la traçabilité et le débogage.
|
||||||
class UserRemoteDataSource {
|
class UserRemoteDataSource {
|
||||||
final http.Client client;
|
final http.Client client;
|
||||||
|
|
||||||
|
/// Constructeur avec injection du client HTTP
|
||||||
UserRemoteDataSource(this.client);
|
UserRemoteDataSource(this.client);
|
||||||
|
|
||||||
// Authentifier l'utilisateur
|
/// Authentifie un utilisateur avec l'email et le mot de passe en clair.
|
||||||
Future<UserModel> authenticateUser(String email, String password, String userId) async {
|
/// Si l'authentification réussit, retourne un objet `UserModel`.
|
||||||
if (email.isEmpty || password.isEmpty) {
|
Future<UserModel> authenticateUser(String email, String password) async {
|
||||||
throw Exception('Email ou mot de passe vide');
|
print("Tentative d'authentification pour l'email : $email");
|
||||||
}
|
|
||||||
|
|
||||||
final response = await client.post(
|
try {
|
||||||
Uri.parse('${Urls.baseUrl}/users/authenticate'),
|
// Requête POST avec l'email et le mot de passe en clair
|
||||||
headers: {'Content-Type': 'application/json'},
|
final response = await client.post(
|
||||||
body: jsonEncode({
|
Uri.parse('${Urls.baseUrl}/users/authenticate'),
|
||||||
'email': email,
|
headers: {'Content-Type': 'application/json'},
|
||||||
'motDePasse': password,
|
body: jsonEncode({
|
||||||
}),
|
'email': email,
|
||||||
);
|
'motDePasse': password, // Le mot de passe est envoyé en clair pour le moment
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
print("Réponse du serveur pour l'authentification : ${response.statusCode} - ${response.body}");
|
||||||
final jsonResponse = json.decode(response.body);
|
|
||||||
return UserModel.fromJson(jsonResponse);
|
if (response.statusCode == 200) {
|
||||||
} else if (response.statusCode == 401) {
|
// Si l'authentification réussit, retourne l'utilisateur
|
||||||
throw AuthenticationException('Email ou mot de passe incorrect');
|
return UserModel.fromJson(jsonDecode(response.body));
|
||||||
} else {
|
} else if (response.statusCode == 401) {
|
||||||
throw ServerException();
|
// Gestion des erreurs d'authentification
|
||||||
|
throw UnauthorizedException();
|
||||||
|
} else {
|
||||||
|
throw ServerException();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Erreur d'authentification : $e");
|
||||||
|
throw Exception("Erreur lors de l'authentification : $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer un utilisateur par ID
|
/// Récupère un utilisateur par son identifiant et logue les étapes.
|
||||||
Future<UserModel> getUser(String id) async {
|
Future<UserModel> getUser(String id) async {
|
||||||
final response = await client.get(Uri.parse('${Urls.baseUrl}/users/$id'));
|
print("Tentative de récupération de l'utilisateur avec l'ID : $id");
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
try {
|
||||||
return UserModel.fromJson(json.decode(response.body));
|
final response = await client.get(Uri.parse('${Urls.baseUrl}/users/$id'));
|
||||||
} else {
|
print("Réponse du serveur pour getUser : ${response.statusCode} - ${response.body}");
|
||||||
throw ServerException();
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return UserModel.fromJson(json.decode(response.body));
|
||||||
|
} else if (response.statusCode == 404) {
|
||||||
|
print("Utilisateur non trouvé.");
|
||||||
|
throw UserNotFoundException();
|
||||||
|
} else {
|
||||||
|
throw ServerException();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Erreur lors de la récupération de l'utilisateur : $e");
|
||||||
|
throw Exception("Erreur lors de la récupération de l'utilisateur : $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Créer un nouvel utilisateur
|
/// Crée un nouvel utilisateur et logue les détails de la requête.
|
||||||
Future<UserModel> createUser(UserModel user) async {
|
Future<UserModel> createUser(UserModel user) async {
|
||||||
final response = await client.post(
|
print("Création d'un nouvel utilisateur : ${user.toJson()}");
|
||||||
Uri.parse('${Urls.baseUrl}/users'),
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: jsonEncode(user.toJson()),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 201) {
|
try {
|
||||||
return UserModel.fromJson(json.decode(response.body));
|
final response = await client.post(
|
||||||
} else {
|
Uri.parse('${Urls.baseUrl}/users'),
|
||||||
throw ServerException();
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: jsonEncode(user.toJson()),
|
||||||
|
);
|
||||||
|
print("Réponse du serveur pour createUser : ${response.statusCode} - ${response.body}");
|
||||||
|
|
||||||
|
if (response.statusCode == 201) {
|
||||||
|
return UserModel.fromJson(json.decode(response.body));
|
||||||
|
} else if (response.statusCode == 409) {
|
||||||
|
// Gestion des conflits (utilisateur déjà existant)
|
||||||
|
throw ConflictException();
|
||||||
|
} else {
|
||||||
|
throw ServerException();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Erreur lors de la création de l'utilisateur : $e");
|
||||||
|
throw Exception("Erreur lors de la création de l'utilisateur : $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mettre à jour un utilisateur
|
/// Met à jour un utilisateur existant et logue les étapes.
|
||||||
Future<UserModel> updateUser(UserModel user) async {
|
Future<UserModel> updateUser(UserModel user) async {
|
||||||
final response = await client.put(
|
print("Mise à jour de l'utilisateur : ${user.toJson()}");
|
||||||
Uri.parse('${Urls.baseUrl}/users/${user.userId}'),
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: jsonEncode(user.toJson()),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
try {
|
||||||
return UserModel.fromJson(json.decode(response.body));
|
final response = await client.put(
|
||||||
} else {
|
Uri.parse('${Urls.baseUrl}/users/${user.userId}'),
|
||||||
throw ServerException();
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: jsonEncode(user.toJson()),
|
||||||
|
);
|
||||||
|
print("Réponse du serveur pour updateUser : ${response.statusCode} - ${response.body}");
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return UserModel.fromJson(json.decode(response.body));
|
||||||
|
} else if (response.statusCode == 404) {
|
||||||
|
// Gestion des cas où l'utilisateur n'est pas trouvé
|
||||||
|
throw UserNotFoundException();
|
||||||
|
} else {
|
||||||
|
throw ServerException();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Erreur lors de la mise à jour de l'utilisateur : $e");
|
||||||
|
throw Exception("Erreur lors de la mise à jour de l'utilisateur : $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supprimer un utilisateur par ID
|
/// Supprime un utilisateur et logue chaque étape.
|
||||||
Future<void> deleteUser(String id) async {
|
Future<void> deleteUser(String id) async {
|
||||||
final response = await client.delete(
|
print("Tentative de suppression de l'utilisateur avec l'ID : $id");
|
||||||
Uri.parse('${Urls.baseUrl}/users/$id'),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode != 204) {
|
try {
|
||||||
throw ServerException();
|
final response = await client.delete(Uri.parse('${Urls.baseUrl}/users/$id'));
|
||||||
|
print("Réponse du serveur pour deleteUser : ${response.statusCode} - ${response.body}");
|
||||||
|
|
||||||
|
if (response.statusCode != 204) {
|
||||||
|
print("Erreur lors de la suppression de l'utilisateur.");
|
||||||
|
throw ServerException();
|
||||||
|
} else {
|
||||||
|
print("Utilisateur supprimé avec succès.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Erreur lors de la suppression de l'utilisateur : $e");
|
||||||
|
throw Exception("Erreur lors de la suppression de l'utilisateur : $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,71 @@
|
|||||||
import 'package:afterwork/data/models/user_model.dart';
|
|
||||||
|
|
||||||
/// Modèle de données représentant un événement.
|
|
||||||
/// Cette classe encapsule toutes les propriétés d'un événement, y compris
|
|
||||||
/// le titre, la description, la date, la localisation, la catégorie, les liens,
|
|
||||||
/// l'image, le créateur, les participants et le statut de l'événement.
|
|
||||||
class EventModel {
|
class EventModel {
|
||||||
final String id; // Identifiant unique de l'événement
|
final String id;
|
||||||
final String title; // Titre de l'événement
|
final String title;
|
||||||
final String description; // Description de l'événement
|
final String description;
|
||||||
final String date; // Date de l'événement
|
final String startDate; // Utiliser startDate au lieu de date, si c'est ce que l'API retourne
|
||||||
final String location; // Localisation de l'événement
|
final String location;
|
||||||
final String category; // Catégorie de l'événement
|
final String category;
|
||||||
final String link; // Lien associé à l'événement
|
final String link;
|
||||||
final String? imageUrl; // URL de l'image de l'événement (optionnel)
|
final String? imageUrl; // Nullable
|
||||||
final UserModel creator; // Créateur de l'événement
|
final String creatorEmail; // Remplacer UserModel si le créateur est un email
|
||||||
final List<UserModel> participants; // Liste des participants à l'événement
|
final List<dynamic> participants; // Si participants est une liste simple
|
||||||
final String status; // Statut de l'événement (e.g., "OPEN", "CLOSED")
|
final String status;
|
||||||
|
final int reactionsCount;
|
||||||
|
final int commentsCount;
|
||||||
|
final int sharesCount;
|
||||||
|
|
||||||
/// Constructeur pour initialiser toutes les propriétés de l'événement.
|
|
||||||
EventModel({
|
EventModel({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.date,
|
required this.startDate,
|
||||||
required this.location,
|
required this.location,
|
||||||
required this.category,
|
required this.category,
|
||||||
required this.link,
|
required this.link,
|
||||||
this.imageUrl,
|
this.imageUrl,
|
||||||
required this.creator,
|
required this.creatorEmail,
|
||||||
required this.participants,
|
required this.participants,
|
||||||
required this.status,
|
required this.status,
|
||||||
|
required this.reactionsCount,
|
||||||
|
required this.commentsCount,
|
||||||
|
required this.sharesCount,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Convertit un objet JSON en `EventModel`.
|
|
||||||
factory EventModel.fromJson(Map<String, dynamic> json) {
|
factory EventModel.fromJson(Map<String, dynamic> json) {
|
||||||
// Log de la conversion depuis JSON
|
|
||||||
print('Conversion de l\'objet JSON en EventModel: ${json['id']}');
|
|
||||||
|
|
||||||
return EventModel(
|
return EventModel(
|
||||||
id: json['id'],
|
id: json['id'],
|
||||||
title: json['title'],
|
title: json['title'],
|
||||||
description: json['description'],
|
description: json['description'],
|
||||||
date: json['date'],
|
startDate: json['startDate'], // Vérifier si c'est bien startDate
|
||||||
location: json['location'],
|
location: json['location'],
|
||||||
category: json['category'],
|
category: json['category'],
|
||||||
link: json['link'] ?? '', // Assure qu'il ne soit pas null
|
link: json['link'] ?? '',
|
||||||
imageUrl: json['imageUrl'] ?? '', // Assure qu'il ne soit pas null
|
imageUrl: json['imageUrl'], // Peut être null
|
||||||
status: json['status'],
|
creatorEmail: json['creatorEmail'], // Email du créateur
|
||||||
creator: UserModel.fromJson(json['creator']),
|
participants: json['participants'] ?? [], // Gérer les participants
|
||||||
participants: json['participants'] != null
|
status: json['status'] ?? 'open', // Par défaut à "open" si non fourni
|
||||||
? (json['participants'] as List)
|
reactionsCount: json['reactionsCount'] ?? 0,
|
||||||
.map((user) => UserModel.fromJson(user))
|
commentsCount: json['commentsCount'] ?? 0,
|
||||||
.toList()
|
sharesCount: json['sharesCount'] ?? 0,
|
||||||
: [], // Si participants est null, retourne une liste vide
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convertit un `EventModel` en objet JSON.
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
// Log de la conversion en JSON
|
|
||||||
print('Conversion de l\'EventModel en objet JSON: $id');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': id,
|
'id': id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
'date': date,
|
'startDate': startDate,
|
||||||
'location': location,
|
'location': location,
|
||||||
'category': category,
|
'category': category,
|
||||||
'link': link,
|
'link': link,
|
||||||
'imageUrl': imageUrl,
|
'imageUrl': imageUrl,
|
||||||
'creator': creator.toJson(),
|
'creatorEmail': creatorEmail,
|
||||||
'participants': participants.map((user) => user.toJson()).toList(),
|
'participants': participants,
|
||||||
'status': status,
|
'status': status,
|
||||||
|
'reactionsCount': reactionsCount,
|
||||||
|
'commentsCount': commentsCount,
|
||||||
|
'sharesCount': sharesCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:afterwork/domain/entities/user.dart';
|
import '../../domain/entities/user.dart';
|
||||||
|
|
||||||
class UserModel extends User {
|
/// Modèle représentant l'utilisateur dans l'application AfterWork.
|
||||||
const UserModel({
|
/// Ce modèle est utilisé pour la conversion JSON et l'interaction avec l'API.
|
||||||
required String userId, // Utilisez `id` pour correspondre à l'entité User
|
class UserModel extends User {
|
||||||
|
UserModel({
|
||||||
|
required String userId,
|
||||||
required String nom,
|
required String nom,
|
||||||
required String prenoms,
|
required String prenoms,
|
||||||
required String email,
|
required String email,
|
||||||
@@ -15,23 +17,25 @@ class UserModel extends User {
|
|||||||
motDePasse: motDePasse,
|
motDePasse: motDePasse,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Factory pour créer un `UserModel` à partir d'un JSON reçu depuis l'API.
|
||||||
factory UserModel.fromJson(Map<String, dynamic> json) {
|
factory UserModel.fromJson(Map<String, dynamic> json) {
|
||||||
return UserModel(
|
return UserModel(
|
||||||
userId: json['id'] ?? '',
|
userId: json['id'] ?? '',
|
||||||
nom: json['nom'] ?? '',
|
nom: json['nom'] ?? 'Inconnu',
|
||||||
prenoms: json['prenoms'] ?? '',
|
prenoms: json['prenoms'] ?? 'Inconnu',
|
||||||
email: json['email'] ?? '',
|
email: json['email'] ?? 'inconnu@example.com',
|
||||||
motDePasse: json['motDePasse'] ?? '',
|
motDePasse: json['motDePasse'] ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convertit le `UserModel` en JSON pour l'envoi vers l'API.
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'id': userId, // Utilisez `id` pour correspondre à l'entité User
|
'id': userId,
|
||||||
'nom': nom,
|
'nom': nom,
|
||||||
'prenoms': prenoms,
|
'prenoms': prenoms,
|
||||||
'email': email,
|
'email': email,
|
||||||
'motDePasse': motDePasse,
|
'motDePasse': motDePasse, // Mot de passe en clair (comme demandé temporairement)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,49 @@
|
|||||||
import 'package:afterwork/domain/entities/user.dart';
|
import 'dart:convert';
|
||||||
import 'package:afterwork/domain/repositories/user_repository.dart';
|
|
||||||
import 'package:afterwork/data/datasources/user_remote_data_source.dart';
|
import 'package:afterwork/data/datasources/user_remote_data_source.dart';
|
||||||
import 'package:afterwork/data/models/user_model.dart';
|
import 'package:afterwork/data/models/user_model.dart';
|
||||||
|
import 'package:afterwork/domain/entities/user.dart';
|
||||||
|
import 'package:afterwork/domain/repositories/user_repository.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import '../../core/constants/urls.dart';
|
||||||
|
|
||||||
|
/// Implémentation du repository des utilisateurs.
|
||||||
|
/// Cette classe fait le lien entre les appels de l'application et les services distants pour les opérations sur les utilisateurs.
|
||||||
class UserRepositoryImpl implements UserRepository {
|
class UserRepositoryImpl implements UserRepository {
|
||||||
final UserRemoteDataSource remoteDataSource;
|
final UserRemoteDataSource remoteDataSource;
|
||||||
|
|
||||||
|
/// Constructeur avec injection de la source de données distante.
|
||||||
UserRepositoryImpl({required this.remoteDataSource});
|
UserRepositoryImpl({required this.remoteDataSource});
|
||||||
|
|
||||||
|
/// Récupère un utilisateur par son ID depuis la source de données distante.
|
||||||
@override
|
@override
|
||||||
Future<User> getUser(String id) async {
|
Future<User> getUser(String id) async {
|
||||||
UserModel userModel = await remoteDataSource.getUser(id);
|
UserModel userModel = await remoteDataSource.getUser(id);
|
||||||
return userModel; // Retourne un UserModel qui est un sous-type de User
|
return userModel; // Retourne un UserModel qui est un sous-type de User.
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<User> authenticateUser(String email, String password, String userId) async {
|
/// Authentifie un utilisateur par email et mot de passe (en clair, temporairement).
|
||||||
UserModel userModel = await remoteDataSource.authenticateUser(email, password, userId);
|
Future<UserModel> authenticateUser(String email, String password) async {
|
||||||
return userModel; // Retourne un UserModel qui est un sous-type de User
|
print("Tentative d'authentification pour l'email : $email");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Requête POST avec les identifiants utilisateur pour l'authentification
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse('${Urls.baseUrl}/users/authenticate'),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: jsonEncode({'email': email, 'motDePasse': password}),
|
||||||
|
);
|
||||||
|
|
||||||
|
print("Réponse du serveur pour l'authentification : ${response.statusCode} - ${response.body}");
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return UserModel.fromJson(jsonDecode(response.body));
|
||||||
|
} else {
|
||||||
|
throw Exception("Erreur lors de l'authentification : ${response.statusCode}");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Erreur d'authentification : $e");
|
||||||
|
throw Exception("Erreur lors de l'authentification : $e");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:crypto/crypto.dart';
|
|
||||||
|
|
||||||
String hashPassword(String password) {
|
|
||||||
var bytes = utf8.encode(password); // Convertir en bytes
|
|
||||||
var digest = sha256.convert(bytes); // Hachage SHA-256
|
|
||||||
return digest.toString(); // Retourner le hash sous forme de chaîne
|
|
||||||
}
|
|
||||||
47
lib/data/services/hash_password_service.dart
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import 'package:flutter_bcrypt/flutter_bcrypt.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:afterwork/core/constants/urls.dart';
|
||||||
|
|
||||||
|
class HashPasswordService {
|
||||||
|
/// Hache le mot de passe en utilisant Bcrypt.
|
||||||
|
/// Renvoie une chaîne hachée sécurisée.
|
||||||
|
Future<String> hashPassword(String email, String password) async {
|
||||||
|
try {
|
||||||
|
print("Tentative de récupération du sel depuis le serveur pour l'email : $email");
|
||||||
|
|
||||||
|
// Récupérer le sel depuis le serveur avec l'email
|
||||||
|
final response = await http.get(Uri.parse('${Urls.baseUrl}/users/salt?email=$email'));
|
||||||
|
|
||||||
|
String salt;
|
||||||
|
if (response.statusCode == 200 && response.body.isNotEmpty) {
|
||||||
|
salt = response.body;
|
||||||
|
print("Sel récupéré depuis le serveur : $salt");
|
||||||
|
} else {
|
||||||
|
// Si le sel n'est pas trouvé, on en génère un
|
||||||
|
salt = await FlutterBcrypt.saltWithRounds(rounds: 12);
|
||||||
|
print("Sel généré : $salt");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hachage du mot de passe avec le sel
|
||||||
|
String hashedPassword = await FlutterBcrypt.hashPw(password: password, salt: salt);
|
||||||
|
print("Mot de passe haché avec succès : $hashedPassword");
|
||||||
|
return hashedPassword;
|
||||||
|
} catch (e) {
|
||||||
|
print("Erreur lors du hachage du mot de passe : $e");
|
||||||
|
throw Exception("Erreur lors du hachage du mot de passe.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> verifyPassword(String password, String hashedPassword) async {
|
||||||
|
try {
|
||||||
|
print("Début de la vérification du mot de passe");
|
||||||
|
bool result = await FlutterBcrypt.verify(password: password, hash: hashedPassword);
|
||||||
|
print("Résultat de la vérification : $result");
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
print("Erreur lors de la vérification du mot de passe : $e");
|
||||||
|
throw Exception("Erreur lors de la vérification du mot de passe.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,50 +1,82 @@
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
/// Classe pour gérer les préférences utilisateur à l'aide de SharedPreferences.
|
||||||
|
/// Permet de stocker et récupérer des informations de manière non sécurisée,
|
||||||
|
/// contrairement au stockage sécurisé qui est utilisé pour des données sensibles.
|
||||||
class PreferencesHelper {
|
class PreferencesHelper {
|
||||||
|
// Initialisation de SharedPreferences en tant que Future
|
||||||
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
|
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
/// Sauvegarde une chaîne de caractères (String) dans les préférences.
|
||||||
Future<void> setString(String key, String value) async {
|
Future<void> setString(String key, String value) async {
|
||||||
|
print("Sauvegarde dans les préférences : clé = $key, valeur = $value");
|
||||||
final prefs = await _prefs;
|
final prefs = await _prefs;
|
||||||
await prefs.setString(key, value);
|
await prefs.setString(key, value);
|
||||||
|
print("Sauvegarde réussie pour la clé : $key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Récupère une chaîne de caractères depuis les préférences.
|
||||||
Future<String?> getString(String key) async {
|
Future<String?> getString(String key) async {
|
||||||
|
print("Récupération depuis les préférences pour la clé : $key");
|
||||||
final prefs = await _prefs;
|
final prefs = await _prefs;
|
||||||
return prefs.getString(key);
|
final value = prefs.getString(key);
|
||||||
|
print("Valeur récupérée pour la clé $key : $value");
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Supprime une entrée dans les préférences.
|
||||||
Future<void> remove(String key) async {
|
Future<void> remove(String key) async {
|
||||||
|
print("Suppression dans les préférences pour la clé : $key");
|
||||||
final prefs = await _prefs;
|
final prefs = await _prefs;
|
||||||
await prefs.remove(key);
|
await prefs.remove(key);
|
||||||
|
print("Suppression réussie pour la clé : $key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sauvegarde l'identifiant utilisateur dans les préférences.
|
||||||
Future<void> saveUserId(String userId) async {
|
Future<void> saveUserId(String userId) async {
|
||||||
|
print("Sauvegarde de l'userId dans les préférences : $userId");
|
||||||
await setString('user_id', userId);
|
await setString('user_id', userId);
|
||||||
|
print("Sauvegarde réussie de l'userId.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Récupère l'identifiant utilisateur depuis les préférences.
|
||||||
Future<String?> getUserId() async {
|
Future<String?> getUserId() async {
|
||||||
|
print("Récupération de l'userId depuis les préférences.");
|
||||||
return await getString('user_id');
|
return await getString('user_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sauvegarde le nom d'utilisateur dans les préférences.
|
||||||
Future<void> saveUserName(String userName) async {
|
Future<void> saveUserName(String userName) async {
|
||||||
|
print("Sauvegarde du userName dans les préférences : $userName");
|
||||||
await setString('user_name', userName);
|
await setString('user_name', userName);
|
||||||
|
print("Sauvegarde réussie du userName.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Récupère le nom d'utilisateur depuis les préférences.
|
||||||
Future<String?> getUserName() async {
|
Future<String?> getUserName() async {
|
||||||
|
print("Récupération du userName depuis les préférences.");
|
||||||
return await getString('user_name');
|
return await getString('user_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sauvegarde le prénom de l'utilisateur dans les préférences.
|
||||||
Future<void> saveUserLastName(String userLastName) async {
|
Future<void> saveUserLastName(String userLastName) async {
|
||||||
|
print("Sauvegarde du userLastName dans les préférences : $userLastName");
|
||||||
await setString('user_last_name', userLastName);
|
await setString('user_last_name', userLastName);
|
||||||
|
print("Sauvegarde réussie du userLastName.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Récupère le prénom de l'utilisateur depuis les préférences.
|
||||||
Future<String?> getUserLastName() async {
|
Future<String?> getUserLastName() async {
|
||||||
|
print("Récupération du userLastName depuis les préférences.");
|
||||||
return await getString('user_last_name');
|
return await getString('user_last_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Supprime toutes les informations utilisateur dans les préférences.
|
||||||
Future<void> clearUserInfo() async {
|
Future<void> clearUserInfo() async {
|
||||||
|
print("Suppression des informations utilisateur (userId, userName, userLastName) des préférences.");
|
||||||
await remove('user_id');
|
await remove('user_id');
|
||||||
await remove('user_name');
|
await remove('user_name');
|
||||||
await remove('user_last_name');
|
await remove('user_last_name');
|
||||||
|
print("Suppression réussie des informations utilisateur.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,78 @@
|
|||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
|
/// Classe pour gérer le stockage sécurisé dans l'application.
|
||||||
|
/// Utilise FlutterSecureStorage pour stocker, lire et supprimer des données sensibles.
|
||||||
class SecureStorage {
|
class SecureStorage {
|
||||||
|
// Instance de FlutterSecureStorage pour gérer le stockage sécurisé
|
||||||
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||||
|
|
||||||
|
/// Écrit une valeur dans le stockage sécurisé avec la clé spécifiée.
|
||||||
Future<void> write(String key, String value) async {
|
Future<void> write(String key, String value) async {
|
||||||
|
print("Écriture dans le stockage sécurisé : clé = $key, valeur = $value");
|
||||||
await _storage.write(key: key, value: value);
|
await _storage.write(key: key, value: value);
|
||||||
|
print("Écriture réussie pour la clé : $key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lit une valeur depuis le stockage sécurisé en fonction de la clé spécifiée.
|
||||||
Future<String?> read(String key) async {
|
Future<String?> read(String key) async {
|
||||||
return await _storage.read(key: key);
|
print("Lecture dans le stockage sécurisé pour la clé : $key");
|
||||||
|
final value = await _storage.read(key: key);
|
||||||
|
print("Valeur lue pour la clé $key : $value");
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Supprime une entrée dans le stockage sécurisé pour la clé spécifiée.
|
||||||
Future<void> delete(String key) async {
|
Future<void> delete(String key) async {
|
||||||
|
print("Suppression dans le stockage sécurisé pour la clé : $key");
|
||||||
await _storage.delete(key: key);
|
await _storage.delete(key: key);
|
||||||
|
print("Suppression réussie pour la clé : $key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sauvegarde l'identifiant utilisateur dans le stockage sécurisé.
|
||||||
Future<void> saveUserId(String userId) async {
|
Future<void> saveUserId(String userId) async {
|
||||||
|
print("Sauvegarde de l'userId dans le stockage sécurisé : $userId");
|
||||||
await write('user_id', userId);
|
await write('user_id', userId);
|
||||||
|
print("Sauvegarde réussie de l'userId.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Récupère l'identifiant utilisateur depuis le stockage sécurisé.
|
||||||
Future<String?> getUserId() async {
|
Future<String?> getUserId() async {
|
||||||
|
print("Récupération de l'userId depuis le stockage sécurisé.");
|
||||||
return await read('user_id');
|
return await read('user_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sauvegarde le nom d'utilisateur dans le stockage sécurisé.
|
||||||
Future<void> saveUserName(String userName) async {
|
Future<void> saveUserName(String userName) async {
|
||||||
|
print("Sauvegarde du userName dans le stockage sécurisé : $userName");
|
||||||
await write('user_name', userName);
|
await write('user_name', userName);
|
||||||
|
print("Sauvegarde réussie du userName.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Récupère le nom d'utilisateur depuis le stockage sécurisé.
|
||||||
Future<String?> getUserName() async {
|
Future<String?> getUserName() async {
|
||||||
|
print("Récupération du userName depuis le stockage sécurisé.");
|
||||||
return await read('user_name');
|
return await read('user_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sauvegarde le prénom de l'utilisateur dans le stockage sécurisé.
|
||||||
Future<void> saveUserLastName(String userLastName) async {
|
Future<void> saveUserLastName(String userLastName) async {
|
||||||
|
print("Sauvegarde du userLastName dans le stockage sécurisé : $userLastName");
|
||||||
await write('user_last_name', userLastName);
|
await write('user_last_name', userLastName);
|
||||||
|
print("Sauvegarde réussie du userLastName.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Récupère le prénom de l'utilisateur depuis le stockage sécurisé.
|
||||||
Future<String?> getUserLastName() async {
|
Future<String?> getUserLastName() async {
|
||||||
|
print("Récupération du userLastName depuis le stockage sécurisé.");
|
||||||
return await read('user_last_name');
|
return await read('user_last_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Supprime toutes les informations utilisateur du stockage sécurisé.
|
||||||
Future<void> deleteUserInfo() async {
|
Future<void> deleteUserInfo() async {
|
||||||
|
print("Suppression des informations utilisateur (userId, userName, userLastName).");
|
||||||
await delete('user_id');
|
await delete('user_id');
|
||||||
await delete('user_name');
|
await delete('user_name');
|
||||||
await delete('user_last_name');
|
await delete('user_last_name');
|
||||||
|
print("Suppression réussie des informations utilisateur.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import 'package:afterwork/domain/entities/user.dart';
|
import 'package:afterwork/domain/entities/user.dart';
|
||||||
|
|
||||||
|
/// Interface pour le dépôt de l'utilisateur.
|
||||||
|
/// Cette interface définit les contrats que doit respecter tout dépôt
|
||||||
|
/// qui gère les données relatives aux utilisateurs.
|
||||||
abstract class UserRepository {
|
abstract class UserRepository {
|
||||||
Future<User> getUser(String id);
|
/// Méthode pour récupérer un utilisateur par son identifiant.
|
||||||
|
/// Cette méthode retourne un objet [User] ou lève une exception en cas d'échec.
|
||||||
|
Future<User> getUser(String id) {
|
||||||
|
print("Appel à la méthode getUser avec l'ID : $id");
|
||||||
|
throw UnimplementedError("Cette méthode doit être implémentée dans une classe concrète.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,28 @@ import 'package:afterwork/domain/entities/user.dart';
|
|||||||
import 'package:afterwork/domain/repositories/user_repository.dart';
|
import 'package:afterwork/domain/repositories/user_repository.dart';
|
||||||
import 'package:afterwork/core/errors/failures.dart';
|
import 'package:afterwork/core/errors/failures.dart';
|
||||||
|
|
||||||
|
/// Classe qui implémente le cas d'utilisation permettant de récupérer un utilisateur par son ID.
|
||||||
|
/// Elle interagit avec le dépôt d'utilisateur pour récupérer les données utilisateur.
|
||||||
class GetUser {
|
class GetUser {
|
||||||
final UserRepository repository;
|
final UserRepository repository; // Référence au dépôt d'utilisateur
|
||||||
|
|
||||||
GetUser(this.repository);
|
/// Constructeur qui prend en paramètre un dépôt d'utilisateur.
|
||||||
|
GetUser(this.repository) {
|
||||||
|
print("Initialisation de GetUser avec le UserRepository.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Méthode pour récupérer un utilisateur par son ID.
|
||||||
|
/// Retourne soit un [User], soit une [Failure] en cas d'erreur.
|
||||||
Future<Either<Failure, User>> call(String id) async {
|
Future<Either<Failure, User>> call(String id) async {
|
||||||
|
print("Appel à GetUser avec l'ID : $id");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Appel au dépôt pour récupérer l'utilisateur
|
||||||
final user = await repository.getUser(id);
|
final user = await repository.getUser(id);
|
||||||
|
print("Utilisateur récupéré avec succès : ${user.userId}");
|
||||||
return Right(user);
|
return Right(user);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print("Erreur lors de la récupération de l'utilisateur : $e");
|
||||||
return Left(ServerFailure());
|
return Left(ServerFailure());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,35 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:afterwork/config/router.dart';
|
import 'package:afterwork/config/router.dart';
|
||||||
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
||||||
import 'package:afterwork/data/providers/user_provider.dart';
|
import 'package:afterwork/data/providers/user_provider.dart';
|
||||||
import 'package:afterwork/data/services/preferences_helper.dart';
|
import 'package:afterwork/data/services/preferences_helper.dart';
|
||||||
import 'package:afterwork/data/services/secure_storage.dart';
|
import 'package:afterwork/data/services/secure_storage.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:afterwork/presentation/state_management/event_bloc.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'core/theme/theme_provider.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
// Initialisez le formatage de la date pour la locale française
|
// Initialisation du format de date en français
|
||||||
await initializeDateFormatting('fr_FR', null);
|
await initializeDateFormatting('fr_FR', null);
|
||||||
|
|
||||||
|
// Initialisation des services nécessaires
|
||||||
final eventRemoteDataSource = EventRemoteDataSource(http.Client());
|
final eventRemoteDataSource = EventRemoteDataSource(http.Client());
|
||||||
|
|
||||||
// Remplacez ici par l'utilisation du stockage sécurisé ou des préférences
|
|
||||||
final SecureStorage secureStorage = SecureStorage();
|
final SecureStorage secureStorage = SecureStorage();
|
||||||
final PreferencesHelper preferencesHelper = PreferencesHelper();
|
final PreferencesHelper preferencesHelper = PreferencesHelper();
|
||||||
|
|
||||||
|
// Récupération des informations stockées
|
||||||
String? userId = await secureStorage.getUserId();
|
String? userId = await secureStorage.getUserId();
|
||||||
String? userName = await preferencesHelper.getUserName();
|
String? userName = await preferencesHelper.getUserName();
|
||||||
String? userLastName = await preferencesHelper.getUserLastName();
|
String? userLastName = await preferencesHelper.getUserLastName();
|
||||||
|
|
||||||
// Si les valeurs sont nulles, vous pouvez définir des valeurs par défaut ou gérer autrement
|
// Gestion des valeurs par défaut si nécessaires
|
||||||
userId ??=
|
userId ??= 'default_user_id';
|
||||||
'default_user_id'; // Remplacer par une valeur par défaut si nécessaire
|
|
||||||
userName ??= 'Default';
|
userName ??= 'Default';
|
||||||
userLastName ??= 'User';
|
userLastName ??= 'User';
|
||||||
|
|
||||||
@@ -57,23 +60,29 @@ class MyApp extends StatelessWidget {
|
|||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) =>
|
create: (_) => UserProvider()..setUser(userId, userName, userLastName),
|
||||||
UserProvider()..setUser(userId, userName, userLastName),
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (_) => ThemeProvider(), // Fournisseur de thème
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => EventBloc(remoteDataSource: eventRemoteDataSource),
|
||||||
),
|
),
|
||||||
// Ajouter d'autres providers ici si nécessaire
|
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
child: Consumer<ThemeProvider>(
|
||||||
title: 'AfterWork',
|
builder: (context, themeProvider, _) {
|
||||||
theme: ThemeData(
|
return MaterialApp(
|
||||||
primarySwatch: Colors.blue,
|
title: 'AfterWork',
|
||||||
),
|
theme: themeProvider.currentTheme,
|
||||||
onGenerateRoute: AppRouter(
|
onGenerateRoute: AppRouter(
|
||||||
eventRemoteDataSource: eventRemoteDataSource,
|
eventRemoteDataSource: eventRemoteDataSource,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
userName: userName,
|
userName: userName,
|
||||||
userLastName: userLastName,
|
userLastName: userLastName,
|
||||||
).generateRoute,
|
).generateRoute,
|
||||||
initialRoute: '/',
|
initialRoute: '/',
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,39 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Écran de gestion des réservations.
|
||||||
|
/// Cet écran permet à l'utilisateur de consulter ses réservations.
|
||||||
|
/// Les logs permettent de tracer les actions de navigation et d'affichage.
|
||||||
class ReservationsScreen extends StatelessWidget {
|
class ReservationsScreen extends StatelessWidget {
|
||||||
const ReservationsScreen({super.key});
|
const ReservationsScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
print("Affichage de l'écran des réservations.");
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Réservations'),
|
title: const Text('Réservations'),
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.blueAccent,
|
||||||
elevation: 0,
|
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: Center(
|
||||||
children: [
|
child: Column(
|
||||||
ListTile(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
leading: const Icon(Icons.event, color: Colors.blueAccent),
|
children: [
|
||||||
title: const Text('Réservation 1', style: TextStyle(color: Colors.white)),
|
const Text(
|
||||||
subtitle: const Text('Détails de la réservation 1', style: TextStyle(color: Colors.white70)),
|
'Aucune réservation trouvée',
|
||||||
onTap: () {
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
// Logique pour afficher les détails de la réservation 1
|
),
|
||||||
},
|
const SizedBox(height: 20),
|
||||||
),
|
ElevatedButton(
|
||||||
ListTile(
|
onPressed: () {
|
||||||
leading: const Icon(Icons.event, color: Colors.blueAccent),
|
print("L'utilisateur a appuyé sur le bouton 'Ajouter une réservation'.");
|
||||||
title: const Text('Réservation 2', style: TextStyle(color: Colors.white)),
|
// Logique pour ajouter une réservation
|
||||||
subtitle: const Text('Détails de la réservation 2', style: TextStyle(color: Colors.white70)),
|
},
|
||||||
onTap: () {
|
child: const Text('Ajouter une réservation'),
|
||||||
// Logique pour afficher les détails de la réservation 2
|
),
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
// Ajoutez d'autres ListTile pour les autres réservations
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.black,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,443 +1,110 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:afterwork/data/models/event_model.dart';
|
|
||||||
import 'package:afterwork/data/models/user_model.dart';
|
|
||||||
import 'package:afterwork/core/constants/urls.dart';
|
|
||||||
import 'package:afterwork/data/services/category_service.dart';
|
|
||||||
import '../location/location_picker_screen.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
/// Classe représentant la boîte de dialogue pour ajouter un nouvel événement.
|
/// Dialogue pour ajouter un nouvel événement.
|
||||||
|
/// Ce widget affiche un formulaire permettant à l'utilisateur de saisir les détails d'un événement.
|
||||||
|
/// Les logs permettent de suivre les actions de l'utilisateur dans ce dialogue.
|
||||||
class AddEventDialog extends StatefulWidget {
|
class AddEventDialog extends StatefulWidget {
|
||||||
final String userId;
|
final String userId;
|
||||||
final String userName;
|
final String userName;
|
||||||
final String userLastName;
|
final String userLastName;
|
||||||
|
|
||||||
const AddEventDialog({
|
const AddEventDialog({
|
||||||
super.key,
|
Key? key,
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.userName,
|
required this.userName,
|
||||||
required this.userLastName,
|
required this.userLastName,
|
||||||
});
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_AddEventDialogState createState() => _AddEventDialogState();
|
_AddEventDialogState createState() => _AddEventDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AddEventDialogState extends State<AddEventDialog> {
|
class _AddEventDialogState extends State<AddEventDialog> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>(); // Clé pour valider le formulaire
|
||||||
|
String _eventName = ''; // Nom de l'événement
|
||||||
// Variables pour stocker les données de l'événement
|
DateTime _selectedDate = DateTime.now(); // Date de l'événement
|
||||||
String _title = '';
|
|
||||||
String _description = '';
|
|
||||||
DateTime? _selectedDate;
|
|
||||||
String? _imagePath;
|
|
||||||
String _location = 'Abidjan';
|
|
||||||
String _category = '';
|
|
||||||
String _link = '';
|
|
||||||
LatLng? _selectedLatLng = const LatLng(5.348722, -3.985038);
|
|
||||||
Map<String, List<String>> _categories = {};
|
|
||||||
List<String> _currentCategories = [];
|
|
||||||
String? _selectedCategoryType;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_loadCategories();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _loadCategories() async {
|
|
||||||
final CategoryService categoryService = CategoryService();
|
|
||||||
final categories = await categoryService.loadCategories();
|
|
||||||
setState(() {
|
|
||||||
_categories = categories;
|
|
||||||
_selectedCategoryType = categories.keys.first;
|
|
||||||
_currentCategories = categories[_selectedCategoryType] ?? [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Dialog(
|
print("Affichage du dialogue d'ajout d'événement.");
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(15.0),
|
|
||||||
),
|
|
||||||
backgroundColor: const Color(0xFF2C2C3E),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Form(
|
|
||||||
key: _formKey,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
_buildTitleField(),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildDescriptionField(),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildDatePicker(),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildLocationField(context),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildCategoryField(),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildImagePicker(),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildLinkField(),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildSubmitButton(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTitleField() {
|
return AlertDialog(
|
||||||
return TextFormField(
|
title: const Text('Ajouter un événement'),
|
||||||
decoration: InputDecoration(
|
content: Form(
|
||||||
labelText: 'Titre',
|
key: _formKey,
|
||||||
labelStyle: const TextStyle(color: Colors.white70),
|
child: Column(
|
||||||
filled: true,
|
mainAxisSize: MainAxisSize.min,
|
||||||
fillColor: Colors.white.withOpacity(0.1),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
prefixIcon: const Icon(Icons.title, color: Colors.white70),
|
|
||||||
),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
print('Erreur: Titre est vide');
|
|
||||||
return 'Veuillez entrer un titre';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
onSaved: (value) {
|
|
||||||
_title = value ?? '';
|
|
||||||
print('Titre sauvegardé: $_title');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDescriptionField() {
|
|
||||||
return TextFormField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Description',
|
|
||||||
labelStyle: const TextStyle(color: Colors.white70),
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.white.withOpacity(0.1),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
prefixIcon: const Icon(Icons.description, color: Colors.white70),
|
|
||||||
),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
maxLines: 3,
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
print('Erreur: Description est vide');
|
|
||||||
return 'Veuillez entrer une description';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
onSaved: (value) {
|
|
||||||
_description = value ?? '';
|
|
||||||
print('Description sauvegardée: $_description');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDatePicker() {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
final DateTime? picked = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: DateTime.now(),
|
|
||||||
firstDate: DateTime.now(),
|
|
||||||
lastDate: DateTime(2101),
|
|
||||||
);
|
|
||||||
if (picked != null && picked != _selectedDate) {
|
|
||||||
setState(() {
|
|
||||||
_selectedDate = picked;
|
|
||||||
print('Date sélectionnée: $_selectedDate');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
print('Date non sélectionnée ou égale à la précédente');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(10.0),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
// Champ pour entrer le nom de l'événement
|
||||||
_selectedDate == null
|
TextFormField(
|
||||||
? 'Sélectionnez une date'
|
decoration: const InputDecoration(labelText: 'Nom de l\'événement'),
|
||||||
: '${_selectedDate!.day}/${_selectedDate!.month}/${_selectedDate!.year}',
|
onSaved: (value) {
|
||||||
style: const TextStyle(color: Colors.white70),
|
_eventName = value ?? '';
|
||||||
|
print("Nom de l'événement saisi : $_eventName");
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
print("Erreur : le champ du nom de l'événement est vide.");
|
||||||
|
return 'Veuillez entrer un nom d\'événement';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// Sélecteur de date pour l'événement
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final selectedDate = await _selectDate(context);
|
||||||
|
if (selectedDate != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedDate = selectedDate;
|
||||||
|
print("Date de l'événement sélectionnée : $_selectedDate");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text('Sélectionner la date : ${_selectedDate.toLocal()}'.split(' ')[0]),
|
||||||
),
|
),
|
||||||
const Icon(Icons.calendar_today, color: Colors.white70),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
actions: [
|
||||||
}
|
// Bouton pour annuler l'ajout de l'événement
|
||||||
|
TextButton(
|
||||||
Widget _buildLocationField(BuildContext context) {
|
onPressed: () {
|
||||||
return GestureDetector(
|
print("L'utilisateur a annulé l'ajout de l'événement.");
|
||||||
onTap: () async {
|
Navigator.of(context).pop();
|
||||||
final LatLng? pickedLocation = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const LocationPickerScreen(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (pickedLocation != null) {
|
|
||||||
setState(() {
|
|
||||||
_selectedLatLng = pickedLocation;
|
|
||||||
_location = '${pickedLocation.latitude}, ${pickedLocation.longitude}';
|
|
||||||
print('Localisation sélectionnée: $_location');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
print('Localisation non sélectionnée');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(10.0),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
_selectedLatLng == null
|
|
||||||
? 'Sélectionnez une localisation'
|
|
||||||
: 'Localisation: $_location',
|
|
||||||
style: const TextStyle(color: Colors.white70),
|
|
||||||
),
|
|
||||||
const Icon(Icons.location_on, color: Colors.white70),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construction du champ de catégorie avec sélection du type et de la catégorie.
|
|
||||||
Widget _buildCategoryField() {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
DropdownButtonFormField<String>(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Type de catégorie',
|
|
||||||
labelStyle: const TextStyle(color: Colors.white70),
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.white.withOpacity(0.1),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
value: _selectedCategoryType,
|
|
||||||
items: _categories.keys.map((String type) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: type,
|
|
||||||
child: Text(type, style: const TextStyle(color: Colors.black)),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (String? newValue) {
|
|
||||||
setState(() {
|
|
||||||
_selectedCategoryType = newValue;
|
|
||||||
_currentCategories = _categories[newValue] ?? [];
|
|
||||||
_category = ''; // Réinitialiser la catégorie sélectionnée
|
|
||||||
print('Type de catégorie sélectionné : $_selectedCategoryType');
|
|
||||||
print('Catégories disponibles pour ce type : $_currentCategories');
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
child: const Text('Annuler'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
// Bouton pour soumettre le formulaire et ajouter l'événement
|
||||||
DropdownButtonFormField<String>(
|
TextButton(
|
||||||
decoration: InputDecoration(
|
onPressed: () {
|
||||||
labelText: 'Catégorie',
|
if (_formKey.currentState?.validate() == true) {
|
||||||
labelStyle: const TextStyle(color: Colors.white70),
|
_formKey.currentState?.save();
|
||||||
filled: true,
|
print("L'utilisateur a ajouté un événement : Nom = $_eventName, Date = $_selectedDate");
|
||||||
fillColor: Colors.white.withOpacity(0.1),
|
Navigator.of(context).pop({
|
||||||
border: const OutlineInputBorder(
|
'eventName': _eventName,
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
'eventDate': _selectedDate,
|
||||||
borderSide: BorderSide.none,
|
});
|
||||||
),
|
|
||||||
),
|
|
||||||
value: _category.isNotEmpty ? _category : null,
|
|
||||||
items: _currentCategories.map((String category) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: category,
|
|
||||||
child: Text(category, style: const TextStyle(color: Colors.black)),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (String? newValue) {
|
|
||||||
setState(() {
|
|
||||||
_category = newValue ?? '';
|
|
||||||
print('Catégorie sélectionnée : $_category');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
print('Erreur: Catégorie non sélectionnée');
|
|
||||||
return 'Veuillez sélectionner une catégorie';
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
},
|
},
|
||||||
|
child: const Text('Ajouter'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildImagePicker() {
|
/// Fonction pour afficher le sélecteur de date
|
||||||
return GestureDetector(
|
Future<DateTime?> _selectDate(BuildContext context) async {
|
||||||
onTap: () {
|
final DateTime? picked = await showDatePicker(
|
||||||
// Logique pour sélectionner une image
|
context: context,
|
||||||
print('Image Picker activé');
|
initialDate: _selectedDate,
|
||||||
},
|
firstDate: DateTime(2000),
|
||||||
child: Container(
|
lastDate: DateTime(2101),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(10.0),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
_imagePath == null
|
|
||||||
? 'Sélectionnez une image'
|
|
||||||
: 'Image sélectionnée: $_imagePath',
|
|
||||||
style: const TextStyle(color: Colors.white70),
|
|
||||||
),
|
|
||||||
const Icon(Icons.image, color: Colors.white70),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildLinkField() {
|
|
||||||
return TextFormField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Lien (optionnel)',
|
|
||||||
labelStyle: const TextStyle(color: Colors.white70),
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.white.withOpacity(0.1),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
prefixIcon: const Icon(Icons.link, color: Colors.white70),
|
|
||||||
),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
onSaved: (value) {
|
|
||||||
_link = value ?? '';
|
|
||||||
print('Lien sauvegardé: $_link');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSubmitButton() {
|
|
||||||
return ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
if (_formKey.currentState!.validate()) {
|
|
||||||
_formKey.currentState!.save();
|
|
||||||
print('Formulaire validé');
|
|
||||||
|
|
||||||
EventModel newEvent = EventModel(
|
|
||||||
id: '',
|
|
||||||
title: _title,
|
|
||||||
description: _description,
|
|
||||||
date: _selectedDate?.toIso8601String() ?? '',
|
|
||||||
location: _location,
|
|
||||||
category: _category,
|
|
||||||
link: _link,
|
|
||||||
imageUrl: _imagePath ?? '',
|
|
||||||
status: 'OPEN',
|
|
||||||
creator: UserModel(
|
|
||||||
userId: widget.userId,
|
|
||||||
nom: widget.userName,
|
|
||||||
prenoms: widget.userLastName,
|
|
||||||
email: '',
|
|
||||||
motDePasse: '',
|
|
||||||
),
|
|
||||||
participants: [
|
|
||||||
UserModel(
|
|
||||||
userId: widget.userId,
|
|
||||||
nom: widget.userName,
|
|
||||||
prenoms: widget.userLastName,
|
|
||||||
email: '',
|
|
||||||
motDePasse: '',
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> eventData = newEvent.toJson();
|
|
||||||
print('Données JSON de l\'événement: $eventData');
|
|
||||||
|
|
||||||
final response = await http.post(
|
|
||||||
Uri.parse('${Urls.baseUrl}/events'),
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: jsonEncode(eventData),
|
|
||||||
);
|
|
||||||
|
|
||||||
print('Statut de la réponse: ${response.statusCode}');
|
|
||||||
print('Réponse brute: ${response.body}');
|
|
||||||
|
|
||||||
if (response.statusCode == 201) {
|
|
||||||
print('Événement créé avec succès');
|
|
||||||
Fluttertoast.showToast(
|
|
||||||
msg: "Événement créé avec succès!",
|
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
|
||||||
gravity: ToastGravity.BOTTOM,
|
|
||||||
backgroundColor: Colors.green,
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 16.0,
|
|
||||||
);
|
|
||||||
Navigator.of(context).pop(); // Fermer la boîte de dialogue
|
|
||||||
} else {
|
|
||||||
print('Erreur lors de la création de l\'événement: ${response.reasonPhrase}');
|
|
||||||
Fluttertoast.showToast(
|
|
||||||
msg: "Erreur lors de la création de l'événement",
|
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
|
||||||
gravity: ToastGravity.BOTTOM,
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 16.0,
|
|
||||||
);
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Erreur: ${response.reasonPhrase}')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print('Le formulaire n\'est pas valide');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF1DBF73),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
|
||||||
minimumSize: const Size(double.infinity, 40),
|
|
||||||
),
|
|
||||||
child: const Text('Ajouter l\'événement', style: TextStyle(color: Colors.white)),
|
|
||||||
);
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
print("Date choisie dans le sélecteur : $picked");
|
||||||
|
}
|
||||||
|
return picked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,35 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Écran des établissements.
|
||||||
|
/// Cet écran affiche une liste des établissements disponibles.
|
||||||
|
/// Les logs permettent de tracer les actions de navigation et d'affichage dans cet écran.
|
||||||
class EstablishmentsScreen extends StatelessWidget {
|
class EstablishmentsScreen extends StatelessWidget {
|
||||||
const EstablishmentsScreen({super.key});
|
const EstablishmentsScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Center(
|
print("Affichage de l'écran des établissements.");
|
||||||
child: Text(
|
|
||||||
'Établissements',
|
return Scaffold(
|
||||||
style: TextStyle(
|
appBar: AppBar(
|
||||||
color: Colors.white,
|
title: const Text('Établissements'),
|
||||||
fontSize: 24,
|
backgroundColor: Colors.blueAccent,
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
),
|
body: ListView.builder(
|
||||||
|
itemCount: 10, // Exemple : 10 établissements fictifs pour l'affichage
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
print("Affichage de l'établissement numéro $index.");
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
leading: const Icon(Icons.location_city),
|
||||||
|
title: Text('Établissement $index'),
|
||||||
|
subtitle: const Text('Description de l\'établissement'),
|
||||||
|
onTap: () {
|
||||||
|
print("L'utilisateur a sélectionné l'établissement numéro $index.");
|
||||||
|
// Logique pour ouvrir les détails de l'établissement
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,429 +1,172 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
import 'package:afterwork/data/models/event_model.dart';
|
||||||
|
import 'package:afterwork/core/utils/date_formatter.dart'; // Importer DateFormatter
|
||||||
import '../../../core/utils/date_formatter.dart';
|
|
||||||
|
|
||||||
/// Widget pour afficher une carte d'événement.
|
/// Widget pour afficher une carte d'événement.
|
||||||
/// Cette classe est utilisée pour afficher les détails d'un événement,
|
|
||||||
/// incluant son titre, sa description, son image, et des actions possibles
|
|
||||||
/// telles que réagir, commenter, partager, participer, et fermer ou rouvrir l'événement.
|
|
||||||
class EventCard extends StatelessWidget {
|
class EventCard extends StatelessWidget {
|
||||||
// Identifiant unique de l'événement
|
final EventModel event;
|
||||||
final String eventId;
|
|
||||||
// Source de données distante pour les opérations sur l'événement
|
|
||||||
final EventRemoteDataSource eventRemoteDataSource;
|
|
||||||
// Identifiant de l'utilisateur
|
|
||||||
final String userId;
|
final String userId;
|
||||||
// Nom de l'utilisateur
|
|
||||||
final String userName;
|
final String userName;
|
||||||
// Prénom de l'utilisateur
|
|
||||||
final String userLastName;
|
final String userLastName;
|
||||||
// URL de l'image de profil de l'utilisateur
|
|
||||||
final String profileImage;
|
|
||||||
// Nom complet de l'utilisateur (nom + prénom)
|
|
||||||
final String name;
|
|
||||||
// Date de publication de l'événement
|
|
||||||
final String datePosted;
|
|
||||||
// Titre de l'événement
|
|
||||||
final String eventTitle;
|
|
||||||
// Description de l'événement
|
|
||||||
final String eventDescription;
|
|
||||||
// URL de l'image de l'événement
|
|
||||||
final String eventImageUrl;
|
|
||||||
// Statut de l'événement (e.g., "OPEN", "CLOSED")
|
|
||||||
final String eventStatus;
|
|
||||||
// Catégorie de l'événement
|
|
||||||
final String eventCategory;
|
|
||||||
// Nombre de réactions à l'événement
|
|
||||||
final int reactionsCount;
|
|
||||||
// Nombre de commentaires sur l'événement
|
|
||||||
final int commentsCount;
|
|
||||||
// Nombre de partages de l'événement
|
|
||||||
final int sharesCount;
|
|
||||||
// Callback pour l'action "Réagir"
|
|
||||||
final VoidCallback onReact;
|
final VoidCallback onReact;
|
||||||
// Callback pour l'action "Commenter"
|
|
||||||
final VoidCallback onComment;
|
final VoidCallback onComment;
|
||||||
// Callback pour l'action "Partager"
|
|
||||||
final VoidCallback onShare;
|
final VoidCallback onShare;
|
||||||
// Callback pour l'action "Participer"
|
|
||||||
final VoidCallback onParticipate;
|
final VoidCallback onParticipate;
|
||||||
// Callback pour afficher plus d'options
|
|
||||||
final VoidCallback onMoreOptions;
|
|
||||||
// Callback pour fermer l'événement
|
|
||||||
final VoidCallback onCloseEvent;
|
final VoidCallback onCloseEvent;
|
||||||
// Callback pour rouvrir l'événement
|
|
||||||
final VoidCallback onReopenEvent;
|
final VoidCallback onReopenEvent;
|
||||||
|
|
||||||
const EventCard({
|
const EventCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.eventId,
|
required this.event,
|
||||||
required this.eventRemoteDataSource,
|
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.userName,
|
required this.userName,
|
||||||
required this.userLastName,
|
required this.userLastName,
|
||||||
required this.profileImage,
|
|
||||||
required this.name,
|
|
||||||
required this.datePosted,
|
|
||||||
required this.eventTitle,
|
|
||||||
required this.eventDescription,
|
|
||||||
required this.eventImageUrl,
|
|
||||||
required this.eventStatus,
|
|
||||||
required this.eventCategory,
|
|
||||||
required this.reactionsCount,
|
|
||||||
required this.commentsCount,
|
|
||||||
required this.sharesCount,
|
|
||||||
required this.onReact,
|
required this.onReact,
|
||||||
required this.onComment,
|
required this.onComment,
|
||||||
required this.onShare,
|
required this.onShare,
|
||||||
required this.onParticipate,
|
required this.onParticipate,
|
||||||
required this.onMoreOptions,
|
|
||||||
required this.onCloseEvent,
|
required this.onCloseEvent,
|
||||||
required this.onReopenEvent,
|
required this.onReopenEvent,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Log du rendu de la carte d'événement
|
return Card(
|
||||||
print('Rendu de l\'EventCard pour l\'événement $eventId avec statut $eventStatus');
|
color: const Color(0xFF2C2C3E),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 10.0),
|
||||||
return AnimatedOpacity(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
|
||||||
opacity: 1.0,
|
child: Padding(
|
||||||
duration: const Duration(milliseconds: 300),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Dismissible(
|
child: Column(
|
||||||
key: ValueKey(eventId),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
direction: eventStatus == 'CLOSED'
|
|
||||||
? DismissDirection.endToStart // Permet de rouvrir avec un swipe à gauche
|
|
||||||
: DismissDirection.startToEnd, // Permet de fermer avec un swipe à droite
|
|
||||||
onDismissed: (direction) {
|
|
||||||
if (direction == DismissDirection.startToEnd) {
|
|
||||||
// Log du déclenchement de la fermeture de l'événement
|
|
||||||
print('Tentative de fermeture de l\'événement $eventId');
|
|
||||||
onCloseEvent();
|
|
||||||
} else if (direction == DismissDirection.endToStart && eventStatus == 'CLOSED') {
|
|
||||||
// Log du déclenchement de la réouverture de l'événement
|
|
||||||
print('Tentative de réouverture de l\'événement $eventId');
|
|
||||||
onReopenEvent();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
background: Container(
|
|
||||||
color: Colors.red,
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
padding: const EdgeInsets.only(left: 20.0),
|
|
||||||
child: const Icon(Icons.delete, color: Colors.white),
|
|
||||||
),
|
|
||||||
secondaryBackground: Container(
|
|
||||||
color: Colors.green,
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
padding: const EdgeInsets.only(right: 20.0),
|
|
||||||
child: const Icon(Icons.replay, color: Colors.white),
|
|
||||||
),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
children: [
|
||||||
Card(
|
_buildHeader(),
|
||||||
color: const Color(0xFF2C2C3E),
|
const SizedBox(height: 10),
|
||||||
margin: const EdgeInsets.symmetric(vertical: 10.0),
|
Text(
|
||||||
shape: RoundedRectangleBorder(
|
event.title,
|
||||||
borderRadius: BorderRadius.circular(15.0),
|
style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildHeader(context),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildEventCategory(),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
_buildEventDetails(),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildEventImage(),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Divider(color: Colors.white.withOpacity(0.2)),
|
|
||||||
_buildInteractionRow(),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
_buildParticipateButton(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
event.description,
|
||||||
|
style: const TextStyle(color: Colors.white70, fontSize: 14),
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_buildEventImage(),
|
||||||
|
const Divider(color: Colors.white24),
|
||||||
|
_buildInteractionRow(),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_buildStatusAndActions(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construire l'en-tête de la carte avec les informations de l'utilisateur.
|
Widget _buildHeader() {
|
||||||
/// Cette méthode affiche l'image de profil, le nom de l'utilisateur, la date
|
// Convertir la date de l'événement (de String à DateTime)
|
||||||
/// de publication de l'événement, et le statut de l'événement.
|
DateTime? eventDate;
|
||||||
Widget _buildHeader(BuildContext context) {
|
try {
|
||||||
// Log du rendu de l'en-tête de la carte
|
eventDate = DateTime.parse(event.startDate);
|
||||||
print('Rendu de l\'en-tête pour l\'événement $eventId');
|
} catch (e) {
|
||||||
// Convertir la date `datePosted` en DateTime si ce n'est pas déjà fait
|
eventDate = null; // Gérer le cas où la date ne serait pas valide
|
||||||
DateTime dateTimePosted = DateTime.parse(datePosted);
|
}
|
||||||
|
|
||||||
// Utiliser le DateFormatter pour formater la date
|
// Utiliser DateFormatter pour afficher une date lisible si elle est valide
|
||||||
String formattedDate = DateFormatter.formatDate(dateTimePosted);
|
String formattedDate = eventDate != null ? DateFormatter.formatDate(eventDate) : 'Date inconnue';
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(backgroundImage: NetworkImage(event.imageUrl ?? 'lib/assets/images/placeholder.png')),
|
||||||
backgroundImage: AssetImage(profileImage),
|
|
||||||
radius: 25,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Text(
|
'$userName $userLastName',
|
||||||
name,
|
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
style: const TextStyle(
|
),
|
||||||
color: Colors.white,
|
const SizedBox(height: 5),
|
||||||
fontSize: 16,
|
Text(
|
||||||
fontWeight: FontWeight.bold,
|
formattedDate, // Utiliser la date formatée ici
|
||||||
),
|
style: const TextStyle(color: Colors.white70, fontSize: 14),
|
||||||
),
|
),
|
||||||
Row(
|
],
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
formattedDate,
|
|
||||||
style: const TextStyle(color: Colors.white70, fontSize: 14),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
_buildStatusBadge(), // Badge de statut aligné sur la même ligne que la date du post
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.more_vert, color: Colors.white),
|
icon: const Icon(Icons.more_vert, color: Colors.white),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Log du déclenchement du bouton "Plus d'options"
|
// Logique d'affichage d'options supplémentaires pour l'événement.
|
||||||
print('Plus d\'options déclenché pour l\'événement $eventId');
|
// Vous pouvez utiliser un menu déroulant ou une boîte de dialogue ici.
|
||||||
onMoreOptions();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (eventStatus != 'CLOSED') // Masquer le bouton de fermeture si l'événement est fermé
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.close, color: Colors.white),
|
|
||||||
onPressed: () {
|
|
||||||
// Log du déclenchement du bouton de fermeture de l'événement
|
|
||||||
print('Tentative de fermeture de l\'événement $eventId');
|
|
||||||
onCloseEvent();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Afficher la catégorie de l'événement au-dessus du titre.
|
|
||||||
/// Cette méthode affiche la catégorie en italique pour distinguer le type d'événement.
|
|
||||||
Widget _buildEventCategory() {
|
|
||||||
// Log du rendu de la catégorie de l'événement
|
|
||||||
print('Affichage de la catégorie pour l\'événement $eventId: $eventCategory');
|
|
||||||
|
|
||||||
return Text(
|
|
||||||
eventCategory,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.blueAccent,
|
|
||||||
fontSize: 14,
|
|
||||||
fontStyle: FontStyle.italic, // Style en italique
|
|
||||||
fontWeight: FontWeight.w400, // Titre fin
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Afficher les détails de l'événement.
|
|
||||||
/// Cette méthode affiche le titre et la description de l'événement.
|
|
||||||
Widget _buildEventDetails() {
|
|
||||||
// Log du rendu des détails de l'événement
|
|
||||||
print('Affichage des détails pour l\'événement $eventId');
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
eventTitle,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
eventDescription,
|
|
||||||
style: const TextStyle(color: Colors.white70, fontSize: 14),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Afficher l'image de l'événement.
|
|
||||||
/// Cette méthode affiche l'image associée à l'événement.
|
|
||||||
Widget _buildEventImage() {
|
Widget _buildEventImage() {
|
||||||
// Log du rendu de l'image de l'événement
|
|
||||||
print('Affichage de l\'image pour l\'événement $eventId');
|
|
||||||
|
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(10.0),
|
borderRadius: BorderRadius.circular(10.0),
|
||||||
child: Image.network(
|
child: Image.network(
|
||||||
eventImageUrl,
|
event.imageUrl ?? 'lib/assets/images/placeholder.png',
|
||||||
height: 180,
|
height: 180,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
// Log de l'erreur lors du chargement de l'image
|
return Image.asset('lib/assets/images/placeholder.png'); // Image par défaut si erreur de chargement
|
||||||
print('Erreur de chargement de l\'image pour l\'événement $eventId: $error');
|
}
|
||||||
return Image.asset(
|
|
||||||
'lib/assets/images/placeholder.png',
|
|
||||||
height: 180,
|
|
||||||
width: double.infinity,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Afficher les icônes d'interaction (réagir, commenter, partager).
|
|
||||||
/// Cette méthode affiche les boutons pour réagir, commenter, et partager l'événement.
|
|
||||||
Widget _buildInteractionRow() {
|
Widget _buildInteractionRow() {
|
||||||
// Log du rendu de la ligne d'interaction de l'événement
|
return Row(
|
||||||
print('Affichage des interactions pour l\'événement $eventId');
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
return Padding(
|
_buildIconButton(Icons.thumb_up_alt_outlined, 'Réagir', event.reactionsCount, onReact),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0), // Réduire le padding vertical
|
_buildIconButton(Icons.comment_outlined, 'Commenter', event.commentsCount, onComment),
|
||||||
child: Row(
|
_buildIconButton(Icons.share_outlined, 'Partager', event.sharesCount, onShare),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround, // Utiliser spaceAround pour réduire l'espace
|
],
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildIconButton(
|
|
||||||
icon: Icons.thumb_up_alt_outlined,
|
|
||||||
label: 'Réagir',
|
|
||||||
count: reactionsCount,
|
|
||||||
onPressed: () {
|
|
||||||
// Log de l'action "Réagir"
|
|
||||||
print('Réaction à l\'événement $eventId');
|
|
||||||
onReact();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildIconButton(
|
|
||||||
icon: Icons.comment_outlined,
|
|
||||||
label: 'Commenter',
|
|
||||||
count: commentsCount,
|
|
||||||
onPressed: () {
|
|
||||||
// Log de l'action "Commenter"
|
|
||||||
print('Commentaire sur l\'événement $eventId');
|
|
||||||
onComment();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildIconButton(
|
|
||||||
icon: Icons.share_outlined,
|
|
||||||
label: 'Partager',
|
|
||||||
count: sharesCount,
|
|
||||||
onPressed: () {
|
|
||||||
// Log de l'action "Partager"
|
|
||||||
print('Partage de l\'événement $eventId');
|
|
||||||
onShare();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bouton d'interaction personnalisé.
|
Widget _buildIconButton(IconData icon, String label, int count, VoidCallback onPressed) {
|
||||||
/// Cette méthode construit un bouton avec une icône et un label pour l'interaction.
|
|
||||||
Widget _buildIconButton({
|
|
||||||
required IconData icon,
|
|
||||||
required String label,
|
|
||||||
required int count,
|
|
||||||
required VoidCallback onPressed,
|
|
||||||
}) {
|
|
||||||
// Log de la construction du bouton d'interaction
|
|
||||||
print('Construction du bouton $label pour l\'événement $eventId');
|
|
||||||
|
|
||||||
return TextButton.icon(
|
return TextButton.icon(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
icon: Icon(icon, color: const Color(0xFF1DBF73), size: 20),
|
icon: Icon(icon, color: const Color(0xFF1DBF73), size: 20),
|
||||||
label: Text(
|
label: Text(
|
||||||
'$label ($count)',
|
'$label ($count)',
|
||||||
style: const TextStyle(color: Colors.white70, fontSize: 12),
|
style: const TextStyle(color: Colors.white70, fontSize: 12),
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bouton pour participer à l'événement.
|
// Widget pour afficher le statut de l'événement et les actions associées (fermer, réouvrir)
|
||||||
/// Cette méthode construit un bouton qui permet de participer à l'événement.
|
Widget _buildStatusAndActions() {
|
||||||
/// Si l'événement est fermé, le bouton est caché.
|
return Row(
|
||||||
Widget _buildParticipateButton() {
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
// Log de la construction du bouton "Participer"
|
children: [
|
||||||
print('Construction du bouton "Participer" pour l\'événement $eventId avec statut $eventStatus');
|
Text(
|
||||||
|
event.status == 'closed' ? 'Événement fermé' : 'Événement ouvert',
|
||||||
// Si l'événement est fermé, ne rien retourner (pas de bouton)
|
style: TextStyle(
|
||||||
if (eventStatus == 'CLOSED') {
|
color: event.status == 'closed' ? Colors.red : Colors.green,
|
||||||
print('L\'événement $eventId est fermé, le bouton "Participer" est caché.');
|
fontSize: 14,
|
||||||
return SizedBox.shrink(); // Retourne un widget vide pour ne pas occuper d'espace
|
fontWeight: FontWeight.bold,
|
||||||
}
|
),
|
||||||
|
|
||||||
// Sinon, retourner le bouton "Participer"
|
|
||||||
return ElevatedButton(
|
|
||||||
onPressed: onParticipate,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF1DBF73),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
event.status == 'closed'
|
||||||
minimumSize: const Size(double.infinity, 40),
|
? ElevatedButton(
|
||||||
),
|
onPressed: onReopenEvent,
|
||||||
child: const Text(
|
child: const Text('Rouvrir l\'événement'),
|
||||||
'Participer',
|
)
|
||||||
style: TextStyle(color: Colors.white),
|
: ElevatedButton(
|
||||||
),
|
onPressed: onCloseEvent,
|
||||||
);
|
child: const Text('Fermer l\'événement'),
|
||||||
}
|
|
||||||
|
|
||||||
/// Construire un badge pour afficher le statut de l'événement.
|
|
||||||
/// Cette méthode affiche un badge avec le statut de l'événement ("OPEN" ou "CLOSED").
|
|
||||||
Widget _buildStatusBadge() {
|
|
||||||
// Log de la construction du badge de statut
|
|
||||||
print('Construction du badge de statut pour l\'événement $eventId: $eventStatus');
|
|
||||||
|
|
||||||
Color badgeColor;
|
|
||||||
switch (eventStatus) {
|
|
||||||
case 'CLOSED':
|
|
||||||
badgeColor = Colors.redAccent;
|
|
||||||
break;
|
|
||||||
case 'OPEN':
|
|
||||||
default:
|
|
||||||
badgeColor = Colors.greenAccent;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: badgeColor.withOpacity(0.7),
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
eventStatus.toUpperCase(),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 10, // Réduction de la taille du texte
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:afterwork/data/models/event_model.dart';
|
import 'package:afterwork/data/models/event_model.dart';
|
||||||
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
import 'package:afterwork/presentation/screens/event/event_card.dart';
|
||||||
import 'event_card.dart';
|
import '../../state_management/event_bloc.dart';
|
||||||
import '../dialogs/add_event_dialog.dart';
|
import '../dialogs/add_event_dialog.dart';
|
||||||
|
|
||||||
/// Écran principal pour afficher les événements.
|
|
||||||
class EventScreen extends StatefulWidget {
|
class EventScreen extends StatefulWidget {
|
||||||
final EventRemoteDataSource eventRemoteDataSource;
|
|
||||||
final String userId;
|
final String userId;
|
||||||
final String userName; // Nom de l'utilisateur
|
final String userName;
|
||||||
final String userLastName; // Prénom de l'utilisateur
|
final String userLastName;
|
||||||
|
|
||||||
const EventScreen({
|
const EventScreen({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.eventRemoteDataSource,
|
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.userName,
|
required this.userName,
|
||||||
required this.userLastName,
|
required this.userLastName,
|
||||||
@@ -24,13 +22,11 @@ class EventScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _EventScreenState extends State<EventScreen> {
|
class _EventScreenState extends State<EventScreen> {
|
||||||
late Future<List<EventModel>> _eventsFuture;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// Récupérer la liste des événements à partir de la source de données distante
|
// Charger les événements lors de l'initialisation
|
||||||
_eventsFuture = widget.eventRemoteDataSource.getAllEvents();
|
context.read<EventBloc>().add(LoadEvents(widget.userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -60,161 +56,98 @@ class _EventScreenState extends State<EventScreen> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (eventData != null) {
|
if (eventData != null) {
|
||||||
try {
|
// Ajouter l'événement en appelant l'API via le bloc
|
||||||
print('Tentative de création d\'un nouvel événement par l\'utilisateur ${widget.userId}');
|
context.read<EventBloc>().add(AddEvent(EventModel.fromJson(eventData)));
|
||||||
await widget.eventRemoteDataSource.createEvent(eventData as EventModel);
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
const SnackBar(content: Text('Événement ajouté avec succès !')),
|
||||||
const SnackBar(content: Text('Événement ajouté avec succès !')),
|
);
|
||||||
);
|
|
||||||
// Réactualiser la liste des événements après création
|
|
||||||
setState(() {
|
|
||||||
_eventsFuture = widget.eventRemoteDataSource.getAllEvents();
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
print('Erreur lors de la création de l\'événement: $e');
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Erreur : $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: FutureBuilder<List<EventModel>>(
|
body: BlocBuilder<EventBloc, EventState>(
|
||||||
future: _eventsFuture,
|
builder: (context, state) {
|
||||||
builder: (context, snapshot) {
|
if (state is EventLoading) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (snapshot.hasError) {
|
} else if (state is EventLoaded) {
|
||||||
print('Erreur lors de la récupération des événements: ${snapshot.error}');
|
final events = state.events;
|
||||||
return Center(child: Text('Erreur: ${snapshot.error}'));
|
if (events.isEmpty) {
|
||||||
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
return const Center(child: Text('Aucun événement disponible.'));
|
||||||
return const Center(child: Text('Aucun événement trouvé.'));
|
}
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
itemCount: events.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final event = events[index];
|
||||||
|
return EventCard(
|
||||||
|
key: ValueKey(event.id),
|
||||||
|
event: event,
|
||||||
|
userId: widget.userId,
|
||||||
|
userName: widget.userName,
|
||||||
|
userLastName: widget.userLastName,
|
||||||
|
onReact: () => _onReact(event.id),
|
||||||
|
onComment: () => _onComment(event.id),
|
||||||
|
onShare: () => _onShare(event.id),
|
||||||
|
onParticipate: () => _onParticipate(event.id),
|
||||||
|
onCloseEvent: () => _onCloseEvent(event.id),
|
||||||
|
onReopenEvent: () => _onReopenEvent(event.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (state is EventError) {
|
||||||
|
return Center(child: Text('Erreur: ${state.message}'));
|
||||||
}
|
}
|
||||||
|
return const Center(child: Text('Aucun événement disponible.'));
|
||||||
final events = snapshot.data!;
|
|
||||||
print('Nombre d\'événements récupérés: ${events.length}');
|
|
||||||
|
|
||||||
return ListView.builder(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
itemCount: events.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final event = events[index];
|
|
||||||
print('Affichage de l\'événement ${event.id}');
|
|
||||||
|
|
||||||
return EventCard(
|
|
||||||
key: ValueKey(event.id),
|
|
||||||
eventRemoteDataSource: widget.eventRemoteDataSource,
|
|
||||||
userId: widget.userId,
|
|
||||||
eventId: event.id,
|
|
||||||
userName: widget.userName,
|
|
||||||
userLastName: widget.userLastName,
|
|
||||||
profileImage: 'lib/assets/images/profile_picture.png',
|
|
||||||
name: '${widget.userName} ${widget.userLastName}',
|
|
||||||
eventCategory: event.category,
|
|
||||||
datePosted: event.date,
|
|
||||||
eventTitle: event.title,
|
|
||||||
eventDescription: event.description,
|
|
||||||
eventImageUrl: event.imageUrl ?? 'lib/assets/images/placeholder.png',
|
|
||||||
eventStatus: event.status,
|
|
||||||
reactionsCount: 120, // Exemple de valeur
|
|
||||||
commentsCount: 45, // Exemple de valeur
|
|
||||||
sharesCount: 30, // Exemple de valeur
|
|
||||||
onReact: () {
|
|
||||||
print('Réaction à l\'événement ${event.id}');
|
|
||||||
},
|
|
||||||
onComment: () {
|
|
||||||
print('Commentaire sur l\'événement ${event.id}');
|
|
||||||
},
|
|
||||||
onShare: () {
|
|
||||||
print('Partage de l\'événement ${event.id}');
|
|
||||||
},
|
|
||||||
onParticipate: () {
|
|
||||||
print('Participation à l\'événement ${event.id}');
|
|
||||||
},
|
|
||||||
onCloseEvent: () => _onCloseEvent(context, event.id, index),
|
|
||||||
onMoreOptions: () {
|
|
||||||
print('Affichage des options pour l\'événement ${event.id}');
|
|
||||||
},
|
|
||||||
onReopenEvent: () => _onReopenEvent(context, event.id, index),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
backgroundColor: const Color(0xFF1E1E2C),
|
backgroundColor: const Color(0xFF1E1E2C),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
// Recharger les événements
|
||||||
|
context.read<EventBloc>().add(LoadEvents(widget.userId));
|
||||||
|
},
|
||||||
|
backgroundColor: const Color(0xFF1DBF73),
|
||||||
|
child: const Icon(Icons.refresh),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Logique pour fermer un événement
|
void _onReact(String eventId) {
|
||||||
void _onCloseEvent(BuildContext context, String eventId, int index) async {
|
print('Réaction à l\'événement $eventId');
|
||||||
try {
|
// Implémentez la logique pour réagir à un événement ici
|
||||||
print('Tentative de fermeture de l\'événement $eventId');
|
|
||||||
|
|
||||||
// Appeler l'API pour fermer l'événement
|
|
||||||
await widget.eventRemoteDataSource.closeEvent(eventId);
|
|
||||||
print('Événement fermé avec succès');
|
|
||||||
|
|
||||||
// Montrer un message de succès AVANT de supprimer l'événement de la liste
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('L\'événement a été fermé avec succès.')),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Supprimez l'événement de la liste après avoir affiché le SnackBar
|
|
||||||
setState(() {
|
|
||||||
_eventsFuture = _eventsFuture.then((events) {
|
|
||||||
events.removeAt(index);
|
|
||||||
return events;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
print('Erreur lors de la fermeture de l\'événement: $e');
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Erreur lors de la fermeture de l\'événement : $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Logique pour rouvrir un événement
|
void _onComment(String eventId) {
|
||||||
void _onReopenEvent(BuildContext context, String eventId, int index) async {
|
print('Commentaire sur l\'événement $eventId');
|
||||||
try {
|
// Implémentez la logique pour commenter un événement ici
|
||||||
print('Tentative de réouverture de l\'événement $eventId');
|
}
|
||||||
await widget.eventRemoteDataSource.reopenEvent(eventId);
|
|
||||||
print('Événement rouvert avec succès');
|
|
||||||
|
|
||||||
// Montrer un message de succès
|
void _onShare(String eventId) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
print('Partage de l\'événement $eventId');
|
||||||
const SnackBar(content: Text('L\'événement a été rouvert avec succès.')),
|
// Implémentez la logique pour partager un événement ici
|
||||||
);
|
}
|
||||||
|
|
||||||
// Mettre à jour le statut de l'événement dans la liste des événements
|
void _onParticipate(String eventId) {
|
||||||
setState(() {
|
print('Participation à l\'événement $eventId');
|
||||||
_eventsFuture = _eventsFuture.then((events) {
|
// Implémentez la logique pour participer à un événement ici
|
||||||
final updatedEvent = EventModel(
|
}
|
||||||
id: events[index].id,
|
|
||||||
title: events[index].title,
|
|
||||||
description: events[index].description,
|
|
||||||
date: events[index].date,
|
|
||||||
location: events[index].location,
|
|
||||||
category: events[index].category,
|
|
||||||
link: events[index].link,
|
|
||||||
imageUrl: events[index].imageUrl,
|
|
||||||
creator: events[index].creator,
|
|
||||||
participants: events[index].participants,
|
|
||||||
status: 'OPEN', // Mettre à jour le statut à 'OPEN'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remplacer l'événement dans la liste
|
void _onCloseEvent(String eventId) {
|
||||||
events[index] = updatedEvent;
|
print('Fermeture de l\'événement $eventId');
|
||||||
return events;
|
// Appeler le bloc pour fermer l'événement
|
||||||
});
|
context.read<EventBloc>().add(CloseEvent(eventId));
|
||||||
});
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
} catch (e) {
|
const SnackBar(content: Text('L\'événement a été fermé avec succès.')),
|
||||||
print('Erreur lors de la réouverture de l\'événement: $e');
|
);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
}
|
||||||
SnackBar(content: Text('Erreur lors de la réouverture de l\'événement : $e')),
|
|
||||||
);
|
void _onReopenEvent(String eventId) {
|
||||||
}
|
print('Réouverture de l\'événement $eventId');
|
||||||
|
// Appeler le bloc pour rouvrir l'événement
|
||||||
|
context.read<EventBloc>().add(ReopenEvent(eventId));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('L\'événement a été rouvert avec succès.')),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,163 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../../core/constants/colors.dart'; // Importez les couleurs dynamiques
|
||||||
|
import '../../widgets/friend_suggestions.dart';
|
||||||
|
import '../../widgets/group_list.dart';
|
||||||
|
import '../../widgets/popular_activity_list.dart';
|
||||||
|
import '../../widgets/quick_action_button.dart';
|
||||||
|
import '../../widgets/recommended_event_list.dart';
|
||||||
|
import '../../widgets/section_header.dart';
|
||||||
|
import '../../widgets/story_section.dart';
|
||||||
|
|
||||||
class HomeContentScreen extends StatelessWidget {
|
class HomeContentScreen extends StatelessWidget {
|
||||||
const HomeContentScreen({super.key});
|
const HomeContentScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Center(
|
final size = MediaQuery.of(context).size;
|
||||||
child: Text(
|
|
||||||
'Accueil',
|
return SingleChildScrollView(
|
||||||
style: TextStyle(
|
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 15.0), // Marges réduites
|
||||||
color: Colors.white,
|
child: Column(
|
||||||
fontSize: 24,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
fontWeight: FontWeight.bold,
|
children: [
|
||||||
|
// Section de bienvenue
|
||||||
|
_buildWelcomeCard(),
|
||||||
|
|
||||||
|
const SizedBox(height: 15), // Espacement vertical réduit
|
||||||
|
|
||||||
|
// Section "Moments populaires"
|
||||||
|
_buildCard(
|
||||||
|
context: context,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SectionHeader(
|
||||||
|
title: 'Moments populaires',
|
||||||
|
icon: Icons.camera_alt,
|
||||||
|
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), // Taille ajustée
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10), // Espace vertical réduit
|
||||||
|
StorySection(size: size),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 15), // Espacement réduit
|
||||||
|
|
||||||
|
// Section des événements recommandés
|
||||||
|
_buildCard(
|
||||||
|
context: context,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SectionHeader(
|
||||||
|
title: 'Événements recommandés',
|
||||||
|
icon: Icons.star,
|
||||||
|
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10), // Espacement réduit
|
||||||
|
RecommendedEventList(size: size),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 15), // Espacement réduit
|
||||||
|
|
||||||
|
// Section des activités populaires
|
||||||
|
_buildCard(
|
||||||
|
context: context,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SectionHeader(
|
||||||
|
title: 'Activités populaires',
|
||||||
|
icon: Icons.local_activity,
|
||||||
|
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10), // Espacement réduit
|
||||||
|
PopularActivityList(size: size),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 15), // Espacement réduit
|
||||||
|
|
||||||
|
// Section des groupes sociaux
|
||||||
|
_buildCard(
|
||||||
|
context: context,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SectionHeader(
|
||||||
|
title: 'Groupes à rejoindre',
|
||||||
|
icon: Icons.group_add,
|
||||||
|
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10), // Espacement réduit
|
||||||
|
GroupList(size: size),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 15), // Espacement réduit
|
||||||
|
|
||||||
|
// Section des suggestions d'amis
|
||||||
|
_buildCard(
|
||||||
|
context: context,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SectionHeader(
|
||||||
|
title: 'Suggestions d’amis',
|
||||||
|
icon: Icons.person_add,
|
||||||
|
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10), // Espacement réduit
|
||||||
|
FriendSuggestions(size: size),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget pour la carte de bienvenue
|
||||||
|
Widget _buildWelcomeCard() {
|
||||||
|
return Card(
|
||||||
|
elevation: 5,
|
||||||
|
color: AppColors.surface, // Utilisation de la couleur dynamique pour la surface
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Bienvenue, Dahoud!',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.textPrimary, // Texte dynamique
|
||||||
|
fontSize: 22, // Taille de police réduite
|
||||||
|
fontWeight: FontWeight.w600, // Poids de police ajusté
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(Icons.waving_hand, color: Colors.orange.shade300, size: 24), // Taille de l'icône ajustée
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Widget générique pour créer une carte design avec des espaces optimisés
|
||||||
|
Widget _buildCard({required BuildContext context, required Widget child}) {
|
||||||
|
return Card(
|
||||||
|
elevation: 3, // Réduction de l'élévation pour un look plus épuré
|
||||||
|
color: AppColors.surface, // Utilisation de la couleur dynamique pour la surface
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), // Coins légèrement arrondis
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0), // Padding interne réduit pour un contenu plus compact
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart'; // Pour ThemeProvider
|
||||||
import 'package:afterwork/presentation/screens/event/event_screen.dart';
|
import 'package:afterwork/presentation/screens/event/event_screen.dart';
|
||||||
import 'package:afterwork/presentation/screens/profile/profile_screen.dart';
|
import 'package:afterwork/presentation/screens/profile/profile_screen.dart';
|
||||||
import 'package:afterwork/presentation/screens/social/social_screen.dart';
|
import 'package:afterwork/presentation/screens/social/social_screen.dart';
|
||||||
import 'package:afterwork/presentation/screens/establishments/establishments_screen.dart';
|
import 'package:afterwork/presentation/screens/establishments/establishments_screen.dart';
|
||||||
import 'package:afterwork/presentation/screens/home/home_content.dart';
|
import 'package:afterwork/presentation/screens/home/home_content.dart';
|
||||||
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
||||||
|
import 'package:afterwork/presentation/screens/notifications/notifications_screen.dart'; // Importez l'écran de notifications
|
||||||
|
|
||||||
|
import '../../../core/constants/colors.dart';
|
||||||
|
import '../../../core/theme/theme_provider.dart'; // Pour basculer le thème
|
||||||
|
|
||||||
/// Classe principale pour l'écran d'accueil de l'application.
|
|
||||||
/// Cette classe gère la navigation entre les différentes sections de l'application
|
|
||||||
/// en utilisant un [TabController] pour contrôler les différents onglets.
|
|
||||||
/// Les actions de l'AppBar sont également personnalisées pour offrir des fonctionnalités
|
|
||||||
/// spécifiques comme la recherche, la publication et la messagerie.
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
final EventRemoteDataSource eventRemoteDataSource;
|
final EventRemoteDataSource eventRemoteDataSource;
|
||||||
final String userId;
|
final String userId;
|
||||||
final String userName;
|
final String userName;
|
||||||
final String userLastName;
|
final String userLastName;
|
||||||
|
final String userProfileImage; // Ajouter un champ pour l'image de profil de l'utilisateur
|
||||||
|
|
||||||
const HomeScreen({
|
const HomeScreen({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -23,6 +24,7 @@ class HomeScreen extends StatefulWidget {
|
|||||||
required this.userId,
|
required this.userId,
|
||||||
required this.userName,
|
required this.userName,
|
||||||
required this.userLastName,
|
required this.userLastName,
|
||||||
|
required this.userProfileImage, // Passer l'image de profil ici
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -35,136 +37,207 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// Initialisation du TabController avec 5 onglets.
|
_tabController = TabController(length: 6, vsync: this); // Ajouter un onglet pour les notifications
|
||||||
_tabController = TabController(length: 5, vsync: this);
|
|
||||||
debugPrint('HomeScreen initialisé avec userId: ${widget.userId}, userName: ${widget.userName}, userLastName: ${widget.userLastName}');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// Nettoyage du TabController pour éviter les fuites de mémoire.
|
|
||||||
_tabController.dispose();
|
_tabController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
debugPrint('HomeScreen dispose appelé');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gestion des sélections dans le menu contextuel de l'AppBar.
|
|
||||||
void _onMenuSelected(BuildContext context, String option) {
|
void _onMenuSelected(BuildContext context, String option) {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case 'Publier':
|
case 'Publier':
|
||||||
debugPrint('Option "Publier" sélectionnée');
|
print('Publier sélectionné');
|
||||||
// Rediriger vers la page de publication.
|
|
||||||
break;
|
break;
|
||||||
case 'Story':
|
case 'Story':
|
||||||
debugPrint('Option "Story" sélectionnée');
|
print('Story sélectionné');
|
||||||
// Rediriger vers la page de création de Story.
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
debugPrint('Option inconnue sélectionnée: $option');
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// Accès au ThemeProvider
|
||||||
|
final themeProvider = Provider.of<ThemeProvider>(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
body: NestedScrollView(
|
||||||
backgroundColor: Colors.black,
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
elevation: 0,
|
return <Widget>[
|
||||||
leading: Padding(
|
SliverAppBar(
|
||||||
padding: const EdgeInsets.all(8.0),
|
backgroundColor: AppColors.backgroundColor, // Gère dynamiquement la couleur d'arrière-plan
|
||||||
child: Image.asset(
|
floating: true,
|
||||||
'lib/assets/images/logo.png', // Chemin correct de votre logo.
|
pinned: true,
|
||||||
height: 40,
|
snap: true,
|
||||||
),
|
elevation: 2, // Réduction de l'élévation pour un design plus léger
|
||||||
),
|
leading: Padding(
|
||||||
actions: [
|
padding: const EdgeInsets.all(4.0), // Réduction du padding
|
||||||
// Bouton pour ajouter du contenu (Publier, Story).
|
child: Image.asset(
|
||||||
CircleAvatar(
|
'lib/assets/images/logo.png',
|
||||||
backgroundColor: Colors.white,
|
height: 40, // Taille réduite du logo
|
||||||
radius: 18,
|
|
||||||
child: PopupMenuButton<String>(
|
|
||||||
onSelected: (value) {
|
|
||||||
_onMenuSelected(context, value);
|
|
||||||
debugPrint('Menu contextuel sélectionné: $value');
|
|
||||||
},
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
const PopupMenuItem(
|
|
||||||
value: 'Publier',
|
|
||||||
child: Text('Publier'),
|
|
||||||
),
|
),
|
||||||
const PopupMenuItem(
|
),
|
||||||
value: 'Story',
|
actions: [
|
||||||
child: Text('Story'),
|
_buildActionIcon(Icons.add, 'Publier', context),
|
||||||
|
_buildActionIcon(Icons.search, 'Rechercher', context),
|
||||||
|
_buildActionIcon(Icons.message, 'Message', context),
|
||||||
|
_buildNotificationsIcon(context, 45),
|
||||||
|
|
||||||
|
// Ajout du bouton pour basculer entre les thèmes
|
||||||
|
Switch(
|
||||||
|
value: themeProvider.isDarkMode,
|
||||||
|
onChanged: (value) {
|
||||||
|
themeProvider.toggleTheme(); // Bascule le thème lorsqu'on clique
|
||||||
|
},
|
||||||
|
activeColor: AppColors.accentColor,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
icon: const Icon(Icons.add, color: Colors.blueAccent, size: 20),
|
bottom: TabBar(
|
||||||
color: Colors.white,
|
controller: _tabController,
|
||||||
),
|
indicatorColor: AppColors.lightPrimary, // Tab active en bleu
|
||||||
),
|
labelStyle: const TextStyle(
|
||||||
const SizedBox(width: 8), // Espacement entre les boutons.
|
fontSize: 12, // Réduction de la taille du texte des onglets
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
unselectedLabelStyle: const TextStyle(
|
||||||
|
fontSize: 11, // Réduction pour les onglets non sélectionnés
|
||||||
|
),
|
||||||
|
// Changement des couleurs pour les tabs non sélectionnées et sélectionnées
|
||||||
|
labelColor: AppColors.lightPrimary, // Tab active en bleu
|
||||||
|
unselectedLabelColor: AppColors.iconSecondary, // Tabs non sélectionnées en blanc
|
||||||
|
|
||||||
// Bouton Recherche.
|
tabs: [
|
||||||
CircleAvatar(
|
const Tab(icon: Icon(Icons.home, size: 24), text: 'Accueil'),
|
||||||
backgroundColor: Colors.white,
|
const Tab(icon: Icon(Icons.event, size: 24), text: 'Événements'),
|
||||||
radius: 18,
|
const Tab(icon: Icon(Icons.location_city, size: 24), text: 'Établissements'),
|
||||||
child: IconButton(
|
const Tab(icon: Icon(Icons.people, size: 24), text: 'Social'),
|
||||||
icon: const Icon(Icons.search, color: Colors.blueAccent, size: 20),
|
const Tab(icon: Icon(Icons.notifications, size: 24), text: 'Notifications'),
|
||||||
onPressed: () {
|
_buildProfileTab(),
|
||||||
debugPrint('Bouton Recherche appuyé');
|
],
|
||||||
// Implémenter la logique de recherche ici.
|
),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
];
|
||||||
const SizedBox(width: 8), // Espacement entre les boutons.
|
},
|
||||||
|
body: TabBarView(
|
||||||
// Bouton Messagerie.
|
|
||||||
CircleAvatar(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
radius: 18,
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.message, color: Colors.blueAccent, size: 20),
|
|
||||||
onPressed: () {
|
|
||||||
debugPrint('Bouton Messagerie appuyé');
|
|
||||||
// Implémenter la logique de messagerie ici.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8), // Espacement entre les boutons.
|
|
||||||
],
|
|
||||||
bottom: TabBar(
|
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
indicatorColor: Colors.blueAccent,
|
children: [
|
||||||
labelColor: Colors.white, // Couleur du texte sélectionné.
|
const HomeContentScreen(),
|
||||||
unselectedLabelColor: Colors.grey[400], // Couleur du texte non sélectionné.
|
EventScreen(
|
||||||
onTap: (index) {
|
userId: widget.userId,
|
||||||
debugPrint('Onglet sélectionné: $index');
|
userName: widget.userName,
|
||||||
},
|
userLastName: widget.userLastName,
|
||||||
tabs: const [
|
),
|
||||||
Tab(icon: Icon(Icons.home), text: 'Accueil'),
|
const EstablishmentsScreen(),
|
||||||
Tab(icon: Icon(Icons.event), text: 'Événements'),
|
const SocialScreen(),
|
||||||
Tab(icon: Icon(Icons.location_city), text: 'Établissements'),
|
const NotificationsScreen(),
|
||||||
Tab(icon: Icon(Icons.people), text: 'Social'),
|
const ProfileScreen(),
|
||||||
Tab(icon: Icon(Icons.person), text: 'Profil'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: TabBarView(
|
);
|
||||||
controller: _tabController,
|
}
|
||||||
|
|
||||||
|
// Widget pour afficher la photo de profil de l'utilisateur dans l'onglet
|
||||||
|
Tab _buildProfileTab() {
|
||||||
|
return Tab(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.blue, // Définir la couleur de la bordure ici
|
||||||
|
width: 2.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 16, // Ajustez la taille si nécessaire
|
||||||
|
backgroundColor: Colors.grey[200], // Couleur de fond pour le cas où l'image ne charge pas
|
||||||
|
child: ClipOval(
|
||||||
|
child: FadeInImage.assetNetwork(
|
||||||
|
placeholder: 'lib/assets/images/user_placeholder.png', // Chemin de l'image par défaut
|
||||||
|
image: widget.userProfileImage,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
imageErrorBuilder: (context, error, stackTrace) {
|
||||||
|
// Si l'image ne charge pas, afficher une image par défaut
|
||||||
|
return Image.asset('lib/assets/images/profile_picture.png', fit: BoxFit.cover);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget pour afficher l'icône de notifications avec un badge si nécessaire
|
||||||
|
Widget _buildNotificationsIcon(BuildContext context, int notificationCount) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none, // Permet de positionner le badge en dehors des limites du Stack
|
||||||
children: [
|
children: [
|
||||||
const HomeContentScreen(), // Contenu de l'accueil.
|
CircleAvatar(
|
||||||
EventScreen(
|
backgroundColor: AppColors.surface,
|
||||||
eventRemoteDataSource: widget.eventRemoteDataSource,
|
radius: 18,
|
||||||
userId: widget.userId,
|
child: IconButton(
|
||||||
userName: widget.userName,
|
icon: const Icon(Icons.notifications, color: AppColors.darkOnPrimary, size: 20),
|
||||||
userLastName: widget.userLastName,
|
onPressed: () {
|
||||||
), // Écran des événements.
|
// Rediriger vers l'écran des notifications
|
||||||
const EstablishmentsScreen(), // Écran des établissements.
|
Navigator.push(
|
||||||
const SocialScreen(), // Écran social.
|
context,
|
||||||
const ProfileScreen(), // Écran du profil.
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const NotificationsScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Affiche le badge si le nombre de notifications est supérieur à 0
|
||||||
|
if (notificationCount > 0)
|
||||||
|
Positioned(
|
||||||
|
right: -6,
|
||||||
|
top: -6,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.red, // Couleur du badge
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: 18,
|
||||||
|
minHeight: 18,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
notificationCount > 99 ? '99+' : '$notificationCount', // Affiche "99+" si le nombre dépasse 99
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.black, // Arrière-plan de l'écran en noir.
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildActionIcon(IconData iconData, String label, BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6.0), // Réduction de l'espacement
|
||||||
|
child: CircleAvatar(
|
||||||
|
backgroundColor: AppColors.surface,
|
||||||
|
radius: 18, // Réduction de la taille des avatars
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(iconData, color: AppColors.darkOnPrimary, size: 20), // Taille réduite de l'icône
|
||||||
|
onPressed: () {
|
||||||
|
_onMenuSelected(context, label);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,82 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
|
||||||
class LocationPickerScreen extends StatelessWidget {
|
/// Écran pour la sélection d'une localisation sur une carte.
|
||||||
const LocationPickerScreen({super.key});
|
/// L'utilisateur peut choisir un lieu en interagissant avec la carte Google Maps.
|
||||||
|
/// Des logs permettent de tracer les actions comme la sélection et l'affichage de la carte.
|
||||||
|
class LocationPickerScreen extends StatefulWidget {
|
||||||
|
const LocationPickerScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LocationPickerScreenState createState() => _LocationPickerScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LocationPickerScreenState extends State<LocationPickerScreen> {
|
||||||
|
LatLng _pickedLocation = const LatLng(37.7749, -122.4194); // Localisation par défaut (San Francisco)
|
||||||
|
late GoogleMapController _mapController; // Contrôleur de la carte Google Maps
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
print('Affichage de l\'écran de sélection de localisation.');
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Sélectionnez une localisation'),
|
title: const Text('Sélectionnez un lieu'),
|
||||||
backgroundColor: const Color(0xFF1E1E2C),
|
backgroundColor: Colors.blueAccent,
|
||||||
),
|
),
|
||||||
body: GoogleMap(
|
body: Column(
|
||||||
initialCameraPosition: const CameraPosition(
|
children: [
|
||||||
target: LatLng(48.8566, 2.3522), // Paris par défaut
|
Expanded(
|
||||||
zoom: 12.0,
|
child: GoogleMap(
|
||||||
),
|
initialCameraPosition: CameraPosition(
|
||||||
markers: <Marker>{
|
target: _pickedLocation,
|
||||||
Marker(
|
zoom: 14,
|
||||||
markerId: const MarkerId('selectedLocation'),
|
),
|
||||||
position: const LatLng(48.8566, 2.3522), // Position par défaut
|
onMapCreated: (controller) {
|
||||||
draggable: true,
|
_mapController = controller;
|
||||||
onDragEnd: (newPosition) {
|
print('Carte Google Maps créée.');
|
||||||
print('Nouvelle position sélectionnée: $newPosition');
|
},
|
||||||
Navigator.of(context).pop(newPosition);
|
onTap: _selectLocation, // Sélection de la localisation sur la carte
|
||||||
},
|
markers: {
|
||||||
)
|
Marker(
|
||||||
},
|
markerId: const MarkerId('pickedLocation'),
|
||||||
onTap: (position) {
|
position: _pickedLocation,
|
||||||
print('Position tapée: $position');
|
),
|
||||||
Navigator.of(context).pop(position);
|
},
|
||||||
},
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
print('Lieu sélectionné : $_pickedLocation');
|
||||||
|
Navigator.of(context).pop(_pickedLocation);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.check),
|
||||||
|
label: const Text('Confirmer la localisation'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
minimumSize: const Size(double.infinity, 50),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// Fonction pour gérer la sélection d'une localisation sur la carte.
|
||||||
|
/// Lorsqu'une localisation est sélectionnée, elle est ajoutée à la carte et les logs sont mis à jour.
|
||||||
|
void _selectLocation(LatLng position) {
|
||||||
|
setState(() {
|
||||||
|
_pickedLocation = position;
|
||||||
|
});
|
||||||
|
print('Localisation sélectionnée : $_pickedLocation');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_mapController.dispose();
|
||||||
|
print('Libération des ressources de la carte.');
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:afterwork/data/datasources/user_remote_data_source.dart';
|
import 'package:afterwork/data/datasources/user_remote_data_source.dart';
|
||||||
import 'package:afterwork/data/models/user_model.dart';
|
import 'package:afterwork/data/models/user_model.dart';
|
||||||
import 'package:afterwork/presentation/screens/home/home_screen.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:afterwork/data/services/hash_password.dart';
|
|
||||||
import 'package:afterwork/data/services/secure_storage.dart';
|
|
||||||
import 'package:afterwork/data/services/preferences_helper.dart';
|
import 'package:afterwork/data/services/preferences_helper.dart';
|
||||||
|
import 'package:afterwork/data/services/secure_storage.dart';
|
||||||
|
import 'package:afterwork/presentation/screens/home/home_screen.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:loading_icon_button/loading_icon_button.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../../../core/theme/theme_provider.dart';
|
||||||
import '../../../data/datasources/event_remote_data_source.dart';
|
import '../../../data/datasources/event_remote_data_source.dart';
|
||||||
|
import '../signup/SignUpScreen.dart';
|
||||||
|
|
||||||
|
/// Écran de connexion pour l'application AfterWork.
|
||||||
|
/// Ce fichier contient des fonctionnalités comme la gestion de la connexion,
|
||||||
|
/// l'authentification avec mot de passe en clair, la gestion des erreurs et un thème jour/nuit.
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
|
|
||||||
@@ -18,87 +25,91 @@ class LoginScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStateMixin {
|
class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStateMixin {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>(); // Clé pour valider le formulaire de connexion.
|
||||||
String _userId = '';
|
|
||||||
String _email = '';
|
|
||||||
String _password = '';
|
|
||||||
bool _isPasswordVisible = false;
|
|
||||||
bool _isSubmitting = false;
|
|
||||||
|
|
||||||
|
// Champs utilisateur
|
||||||
|
String _email = ''; // Email de l'utilisateur
|
||||||
|
String _password = ''; // Mot de passe de l'utilisateur
|
||||||
|
|
||||||
|
// États de gestion
|
||||||
|
bool _isPasswordVisible = false; // Pour afficher/masquer le mot de passe
|
||||||
|
bool _isSubmitting = false; // Indicateur pour l'état de soumission du formulaire
|
||||||
|
bool _showErrorMessage = false; // Affichage des erreurs
|
||||||
|
|
||||||
|
// Services pour les opérations
|
||||||
final UserRemoteDataSource _userRemoteDataSource = UserRemoteDataSource(http.Client());
|
final UserRemoteDataSource _userRemoteDataSource = UserRemoteDataSource(http.Client());
|
||||||
final SecureStorage _secureStorage = SecureStorage();
|
final SecureStorage _secureStorage = SecureStorage();
|
||||||
final PreferencesHelper _preferencesHelper = PreferencesHelper();
|
final PreferencesHelper _preferencesHelper = PreferencesHelper();
|
||||||
|
|
||||||
late AnimationController _controller;
|
// Contrôleur pour le bouton de chargement
|
||||||
late Animation<double> _buttonScaleAnimation;
|
final _btnController = LoadingButtonController();
|
||||||
|
|
||||||
|
// Contrôleur d'animation pour la transition des écrans
|
||||||
|
late AnimationController _animationController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller = AnimationController(
|
_animationController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 500),
|
||||||
);
|
|
||||||
_buttonScaleAnimation = Tween<double>(begin: 1.0, end: 1.1).animate(
|
|
||||||
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
|
|
||||||
);
|
);
|
||||||
|
print("Contrôleur d'animation initialisé.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.dispose();
|
_animationController.dispose();
|
||||||
|
print("Ressources d'animation libérées.");
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Afficher/Masquer le mot de passe
|
/// Fonction pour basculer la visibilité du mot de passe
|
||||||
void _togglePasswordVisibility() {
|
void _togglePasswordVisibility() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isPasswordVisible = !_isPasswordVisible;
|
_isPasswordVisible = !_isPasswordVisible;
|
||||||
});
|
});
|
||||||
|
print("Visibilité du mot de passe basculée: $_isPasswordVisible");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Soumission du formulaire d'authentification
|
/// Fonction pour afficher un toast via FlutterToast
|
||||||
void _submit() async {
|
void _showToast(String message) {
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: message,
|
||||||
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
timeInSecForIosWeb: 1,
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
textColor: Colors.white,
|
||||||
|
fontSize: 16.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fonction soumettre le formulaire
|
||||||
|
Future<void> _submit() async {
|
||||||
|
print("Tentative de soumission du formulaire de connexion.");
|
||||||
|
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isSubmitting = true;
|
_isSubmitting = true;
|
||||||
|
_showErrorMessage = false;
|
||||||
});
|
});
|
||||||
_formKey.currentState!.save();
|
_formKey.currentState!.save();
|
||||||
|
|
||||||
print("===== DEBUT DE LA SOUMISSION DU FORMULAIRE =====");
|
|
||||||
print("Email: $_email");
|
|
||||||
print("Mot de passe: $_password");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
print('Début de l\'authentification'); // Débogage
|
_btnController.start();
|
||||||
|
final UserModel user = await _userRemoteDataSource.authenticateUser(_email, _password);
|
||||||
|
if (user == null) {
|
||||||
|
throw Exception("L'utilisateur n'a pas été trouvé ou l'authentification a échoué.");
|
||||||
|
}
|
||||||
|
|
||||||
// Hachage du mot de passe avec SHA-256
|
print("Utilisateur authentifié : ${user.userId}");
|
||||||
String hashedPassword = hashPassword(_password);
|
|
||||||
print("Mot de passe haché: $hashedPassword");
|
|
||||||
|
|
||||||
// Authentification via l'API avec un timeout
|
|
||||||
UserModel user = await _userRemoteDataSource
|
|
||||||
.authenticateUser(_email, hashedPassword, "unique_user_id")
|
|
||||||
.timeout(
|
|
||||||
Duration(seconds: 10),
|
|
||||||
onTimeout: () {
|
|
||||||
throw TimeoutException('Le temps de connexion a expiré. Veuillez réessayer.');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
print('Connexion réussie : ${user.userId} - ${user.email}');
|
|
||||||
|
|
||||||
// Sauvegarde des données de l'utilisateur après authentification
|
|
||||||
await _secureStorage.saveUserId(user.userId);
|
await _secureStorage.saveUserId(user.userId);
|
||||||
await _preferencesHelper.saveUserName(user.nom);
|
await _preferencesHelper.saveUserName(user.nom);
|
||||||
await _preferencesHelper.saveUserLastName(user.prenoms);
|
await _preferencesHelper.saveUserLastName(user.prenoms);
|
||||||
|
_showToast("Connexion réussie !");
|
||||||
|
|
||||||
print("===== SAUVEGARDE DES DONNÉES UTILISATEUR =====");
|
// Navigation vers la page d'accueil
|
||||||
print("User ID: ${user.userId}");
|
|
||||||
print("User Name: ${user.nom}");
|
|
||||||
print("User Last Name: ${user.prenoms}");
|
|
||||||
|
|
||||||
// Navigation vers l'écran d'accueil
|
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@@ -107,44 +118,74 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
userId: user.userId,
|
userId: user.userId,
|
||||||
userName: user.nom,
|
userName: user.nom,
|
||||||
userLastName: user.prenoms,
|
userLastName: user.prenoms,
|
||||||
|
userProfileImage: 'lib/assets/images/profile_picture.png',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
print("===== NAVIGATION VERS HOME SCREEN =====");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur lors de la connexion: $e');
|
print("Erreur lors de l'authentification : $e");
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
_btnController.error();
|
||||||
SnackBar(content: Text('Erreur : ${e.toString()}')),
|
_showToast("Erreur lors de la connexion : ${e.toString()}");
|
||||||
);
|
setState(() {
|
||||||
|
_showErrorMessage = true;
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
print('Fin du processus d\'authentification'); // Débogage
|
_btnController.reset();
|
||||||
setState(() {
|
setState(() {
|
||||||
_isSubmitting = false;
|
_isSubmitting = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print("===== FORMULAIRE NON VALIDE =====");
|
print("Échec de validation du formulaire.");
|
||||||
|
_btnController.reset();
|
||||||
|
_showToast("Veuillez vérifier les informations saisies.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
final size = MediaQuery.of(context).size;
|
final size = MediaQuery.of(context).size;
|
||||||
|
final themeProvider = Provider.of<ThemeProvider>(context);
|
||||||
|
bool isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom != 0;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
// Arrière-plan avec dégradé
|
AnimatedContainer(
|
||||||
Container(
|
duration: const Duration(seconds: 3),
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [Color(0xFF4A90E2), Color(0xFF9013FE)],
|
colors: [
|
||||||
|
theme.colorScheme.primary,
|
||||||
|
theme.colorScheme.secondary
|
||||||
|
],
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Contenu de la page
|
if (_isSubmitting)
|
||||||
|
const Center(
|
||||||
|
child: SpinKitFadingCircle(
|
||||||
|
color: Colors.white,
|
||||||
|
size: 50.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 40,
|
||||||
|
right: 20,
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
themeProvider.isDarkMode ? Icons.dark_mode : Icons.light_mode,
|
||||||
|
color: theme.iconTheme.color,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
themeProvider.toggleTheme();
|
||||||
|
print("Thème basculé : ${themeProvider.isDarkMode ? 'Sombre' : 'Clair'}");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
Center(
|
Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@@ -153,148 +194,179 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// Logo animé
|
Image.asset(
|
||||||
AnimatedBuilder(
|
'lib/assets/images/logo.png',
|
||||||
animation: _controller,
|
height: size.height * 0.25,
|
||||||
builder: (context, child) {
|
|
||||||
return Transform.scale(
|
|
||||||
scale: _buttonScaleAnimation.value,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: GestureDetector(
|
|
||||||
onTapDown: (_) => _controller.forward(),
|
|
||||||
onTapUp: (_) => _controller.reverse(),
|
|
||||||
child: Image.asset(
|
|
||||||
'lib/assets/images/logo.png',
|
|
||||||
height: size.height * 0.2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
const Text(
|
Text(
|
||||||
'Bienvenue sur AfterWork',
|
'Bienvenue sur AfterWork',
|
||||||
style: TextStyle(
|
style: theme.textTheme.titleLarge,
|
||||||
fontSize: 26,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
shadows: [
|
|
||||||
Shadow(
|
|
||||||
offset: Offset(0, 2),
|
|
||||||
blurRadius: 6,
|
|
||||||
color: Colors.black26,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 40),
|
const SizedBox(height: 40),
|
||||||
// Champ Email
|
_buildTextFormField(
|
||||||
TextFormField(
|
label: 'Email',
|
||||||
decoration: InputDecoration(
|
icon: Icons.email,
|
||||||
labelText: 'Email',
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.white.withOpacity(0.1),
|
|
||||||
labelStyle: const TextStyle(color: Colors.white),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10.0),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
prefixIcon: const Icon(Icons.email, color: Colors.white),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
print("Erreur: Le champ email est vide");
|
print("Erreur : champ email vide.");
|
||||||
return 'Veuillez entrer votre email';
|
return 'Veuillez entrer votre email';
|
||||||
}
|
}
|
||||||
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
|
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
|
||||||
print("Erreur: Le format de l'email est invalide");
|
print("Erreur : email invalide.");
|
||||||
return 'Veuillez entrer un email valide';
|
return 'Veuillez entrer un email valide';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onSaved: (value) {
|
onSaved: (value) {
|
||||||
_email = value ?? ''; // Utiliser une chaîne vide si value est null
|
_email = value!;
|
||||||
print("Email sauvegardé: $_email");
|
print("Email enregistré : $_email");
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
// Champ Mot de passe
|
_buildTextFormField(
|
||||||
TextFormField(
|
label: 'Mot de passe',
|
||||||
decoration: InputDecoration(
|
icon: Icons.lock,
|
||||||
labelText: 'Mot de passe',
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.white.withOpacity(0.1),
|
|
||||||
labelStyle: const TextStyle(color: Colors.white),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10.0),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
prefixIcon: const Icon(Icons.lock, color: Colors.white),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
_isPasswordVisible ? Icons.visibility : Icons.visibility_off,
|
|
||||||
color: Colors.white),
|
|
||||||
onPressed: _togglePasswordVisibility,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
obscureText: !_isPasswordVisible,
|
obscureText: !_isPasswordVisible,
|
||||||
style: const TextStyle(color: Colors.white),
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_isPasswordVisible
|
||||||
|
? Icons.visibility
|
||||||
|
: Icons.visibility_off,
|
||||||
|
color: theme.iconTheme.color,
|
||||||
|
),
|
||||||
|
onPressed: _togglePasswordVisibility,
|
||||||
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
print("Erreur: Le champ mot de passe est vide");
|
print("Erreur : champ mot de passe vide.");
|
||||||
return 'Veuillez entrer votre mot de passe';
|
return 'Veuillez entrer votre mot de passe';
|
||||||
}
|
}
|
||||||
if (value.length < 6) {
|
if (value.length < 6) {
|
||||||
print("Erreur: Le mot de passe est trop court");
|
print("Erreur : mot de passe trop court.");
|
||||||
return 'Le mot de passe doit comporter au moins 6 caractères';
|
return 'Le mot de passe doit comporter au moins 6 caractères';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onSaved: (value) {
|
onSaved: (value) {
|
||||||
_password = value ?? ''; // Utiliser une chaîne vide si value est null
|
_password = value!;
|
||||||
print("Mot de passe sauvegardé: $_password");
|
print("Mot de passe enregistré.");
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 30),
|
||||||
// Bouton de connexion avec animation de soumission
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: size.width * 0.85,
|
||||||
child: ElevatedButton(
|
child: LoadingButton(
|
||||||
style: ElevatedButton.styleFrom(
|
controller: _btnController,
|
||||||
padding: const EdgeInsets.all(16.0),
|
onPressed: _isSubmitting ? null : _submit,
|
||||||
textStyle: const TextStyle(fontSize: 18),
|
iconData: Icons.login,
|
||||||
backgroundColor: _isSubmitting ? Colors.grey : Colors.blueAccent,
|
iconColor: theme.colorScheme.onPrimary,
|
||||||
shape: RoundedRectangleBorder(
|
child: Text(
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
'Connexion',
|
||||||
|
style: theme.textTheme.bodyLarge!.copyWith(
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: _isSubmitting ? null : _submit,
|
|
||||||
child: _isSubmitting
|
|
||||||
? const CircularProgressIndicator(color: Colors.white)
|
|
||||||
: const Text('Connexion'),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
// Lien pour s'inscrire
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Naviguer vers la page d'inscription
|
|
||||||
print("Redirection vers la page d'inscription");
|
print("Redirection vers la page d'inscription");
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => SignUpScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: Text(
|
||||||
'Pas encore de compte ? Inscrivez-vous',
|
'Pas encore de compte ? Inscrivez-vous',
|
||||||
style: TextStyle(color: Colors.white),
|
style: theme.textTheme.bodyMedium!
|
||||||
|
.copyWith(color: Colors.white70),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
print("Mot de passe oublié");
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'Mot de passe oublié ?',
|
||||||
|
style: theme.textTheme.bodyMedium!
|
||||||
|
.copyWith(color: Colors.white70),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_showErrorMessage)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(top: 20),
|
||||||
|
child: Text(
|
||||||
|
'Erreur lors de la connexion. Veuillez vérifier vos identifiants.',
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 16),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
AnimatedPositioned(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
bottom: isKeyboardVisible ? 0 : 20,
|
||||||
|
left: isKeyboardVisible ? 20 : 0,
|
||||||
|
right: isKeyboardVisible ? 20 : 0,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: isKeyboardVisible
|
||||||
|
? MainAxisAlignment.spaceBetween
|
||||||
|
: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'lib/assets/images/logolionsdev.png',
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
if (isKeyboardVisible)
|
||||||
|
Text(
|
||||||
|
'© 2024 LionsDev',
|
||||||
|
style: theme.textTheme.bodyMedium!
|
||||||
|
.copyWith(color: Colors.white70),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Widget réutilisable pour les champs de texte avec validation et design amélioré
|
||||||
|
Widget _buildTextFormField({
|
||||||
|
required String label,
|
||||||
|
required IconData icon,
|
||||||
|
bool obscureText = false,
|
||||||
|
Widget? suffixIcon,
|
||||||
|
required FormFieldValidator<String> validator,
|
||||||
|
required FormFieldSetter<String> onSaved,
|
||||||
|
}) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return TextFormField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: label,
|
||||||
|
filled: true,
|
||||||
|
fillColor: theme.inputDecorationTheme.fillColor,
|
||||||
|
labelStyle: theme.textTheme.bodyMedium,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(icon, color: theme.iconTheme.color),
|
||||||
|
suffixIcon: suffixIcon,
|
||||||
|
),
|
||||||
|
obscureText: obscureText,
|
||||||
|
style: theme.textTheme.bodyLarge,
|
||||||
|
validator: validator,
|
||||||
|
onSaved: onSaved,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class NotificationsScreen extends StatelessWidget {
|
||||||
|
const NotificationsScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Notifications'),
|
||||||
|
backgroundColor: Colors.blueAccent,
|
||||||
|
),
|
||||||
|
body: const Center(
|
||||||
|
child: Text('Liste des notifications'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,117 +1,147 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../../core/constants/colors.dart';
|
||||||
|
|
||||||
class ProfileScreen extends StatelessWidget {
|
class ProfileScreen extends StatelessWidget {
|
||||||
const ProfileScreen({super.key});
|
const ProfileScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
print("Affichage de l'écran de profil.");
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
backgroundColor: AppColors.backgroundColor,
|
||||||
title: const Text('Profil',
|
body: CustomScrollView(
|
||||||
style: TextStyle(
|
slivers: [
|
||||||
color: Color(0xFF1DBF73), // Définit la couleur verte du texte
|
SliverAppBar(
|
||||||
),
|
expandedHeight: 200.0,
|
||||||
),
|
floating: false,
|
||||||
backgroundColor: const Color(0xFF1E1E2C),
|
pinned: true,
|
||||||
actions: [
|
backgroundColor: AppColors.darkPrimary,
|
||||||
IconButton(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
icon: const Icon(Icons.settings, color: Colors.white),
|
title: Text(
|
||||||
onPressed: () {
|
'Profil',
|
||||||
// Naviguer vers la page des paramètres
|
style: TextStyle(
|
||||||
},
|
color: AppColors.accentColor,
|
||||||
),
|
fontSize: 20.0,
|
||||||
],
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
body: ListView(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
children: [
|
|
||||||
_buildUserInfoCard(),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildEditOptionsCard(),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildStatisticsSectionCard(),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildExpandableSectionCard(
|
|
||||||
title: 'Historique',
|
|
||||||
icon: Icons.history,
|
|
||||||
children: [
|
|
||||||
_buildAnimatedListTile(
|
|
||||||
icon: Icons.event_note,
|
|
||||||
label: 'Historique des Événements',
|
|
||||||
onTap: () {
|
|
||||||
// Naviguer vers l'historique des événements
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
_buildAnimatedListTile(
|
background: Image.asset(
|
||||||
icon: Icons.history,
|
'lib/assets/images/profile_picture.png',
|
||||||
label: 'Historique des Publications',
|
fit: BoxFit.cover,
|
||||||
onTap: () {
|
|
||||||
// Naviguer vers l'historique des publications
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
_buildAnimatedListTile(
|
),
|
||||||
icon: Icons.bookmark,
|
actions: [
|
||||||
label: 'Historique de Réservations',
|
IconButton(
|
||||||
onTap: () {
|
icon: const Icon(Icons.settings, color: Colors.white),
|
||||||
// Naviguer vers l'historique des réservations
|
onPressed: () {
|
||||||
|
print("Bouton des paramètres cliqué.");
|
||||||
|
// Logique de navigation vers les paramètres
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
SliverList(
|
||||||
_buildExpandableSectionCard(
|
delegate: SliverChildListDelegate(
|
||||||
title: 'Préférences et Paramètres',
|
[
|
||||||
icon: Icons.settings,
|
const SizedBox(height: 10),
|
||||||
children: [
|
_buildUserInfoCard(),
|
||||||
_buildAnimatedListTile(
|
const SizedBox(height: 10),
|
||||||
icon: Icons.privacy_tip,
|
_buildEditOptionsCard(),
|
||||||
label: 'Paramètres de confidentialité',
|
const SizedBox(height: 10),
|
||||||
onTap: () {
|
_buildStatisticsSectionCard(),
|
||||||
// Naviguer vers les paramètres de confidentialité
|
const SizedBox(height: 10),
|
||||||
},
|
_buildExpandableSectionCard(
|
||||||
),
|
title: 'Historique',
|
||||||
_buildAnimatedListTile(
|
icon: Icons.history,
|
||||||
icon: Icons.notifications,
|
children: [
|
||||||
label: 'Notifications',
|
_buildAnimatedListTile(
|
||||||
onTap: () {
|
icon: Icons.event_note,
|
||||||
// Naviguer vers les paramètres de notification
|
label: 'Historique des Événements',
|
||||||
},
|
onTap: () {
|
||||||
),
|
print("Accès à l'historique des événements.");
|
||||||
_buildAnimatedListTile(
|
// Logique de navigation vers l'historique des événements
|
||||||
icon: Icons.language,
|
},
|
||||||
label: 'Langue de l\'application',
|
),
|
||||||
onTap: () {
|
_buildAnimatedListTile(
|
||||||
// Naviguer vers les paramètres de langue
|
icon: Icons.history,
|
||||||
},
|
label: 'Historique des Publications',
|
||||||
),
|
onTap: () {
|
||||||
_buildAnimatedListTile(
|
print("Accès à l'historique des publications.");
|
||||||
icon: Icons.format_paint,
|
// Logique de navigation vers l'historique des publications
|
||||||
label: 'Thème de l\'application',
|
},
|
||||||
onTap: () {
|
),
|
||||||
// Naviguer vers les paramètres de thème
|
_buildAnimatedListTile(
|
||||||
},
|
icon: Icons.bookmark,
|
||||||
),
|
label: 'Historique de Réservations',
|
||||||
],
|
onTap: () {
|
||||||
|
print("Accès à l'historique des réservations.");
|
||||||
|
// Logique de navigation vers l'historique des réservations
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_buildExpandableSectionCard(
|
||||||
|
title: 'Préférences et Paramètres',
|
||||||
|
icon: Icons.settings,
|
||||||
|
children: [
|
||||||
|
_buildAnimatedListTile(
|
||||||
|
icon: Icons.privacy_tip,
|
||||||
|
label: 'Paramètres de confidentialité',
|
||||||
|
onTap: () {
|
||||||
|
print("Accès aux paramètres de confidentialité.");
|
||||||
|
// Logique de navigation vers les paramètres de confidentialité
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_buildAnimatedListTile(
|
||||||
|
icon: Icons.notifications,
|
||||||
|
label: 'Notifications',
|
||||||
|
onTap: () {
|
||||||
|
print("Accès aux paramètres de notifications.");
|
||||||
|
// Logique de navigation vers les notifications
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_buildAnimatedListTile(
|
||||||
|
icon: Icons.language,
|
||||||
|
label: 'Langue de l\'application',
|
||||||
|
onTap: () {
|
||||||
|
print("Accès aux paramètres de langue.");
|
||||||
|
// Logique de navigation vers les paramètres de langue
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_buildAnimatedListTile(
|
||||||
|
icon: Icons.format_paint,
|
||||||
|
label: 'Thème de l\'application',
|
||||||
|
onTap: () {
|
||||||
|
print("Accès aux paramètres de thème.");
|
||||||
|
// Logique de navigation vers les paramètres de thème
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_buildSupportSectionCard(),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_buildAccountDeletionCard(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildSupportSectionCard(),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildAccountDeletionCard(context),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: const Color(0xFF1E1E2C),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildUserInfoCard() {
|
Widget _buildUserInfoCard() {
|
||||||
return Card(
|
return Card(
|
||||||
color: const Color(0xFF292B37),
|
color: AppColors.cardColor,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
|
elevation: 2,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 50,
|
radius: 50,
|
||||||
backgroundImage: AssetImage('lib/assets/images/profile_picture.png'),
|
backgroundImage: AssetImage('lib/assets/images/profile_picture.png'),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
@@ -152,29 +182,33 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildEditOptionsCard() {
|
Widget _buildEditOptionsCard() {
|
||||||
return Card(
|
return Card(
|
||||||
color: const Color(0xFF292B37),
|
color: AppColors.cardColor,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
|
elevation: 2,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildAnimatedListTile(
|
_buildAnimatedListTile(
|
||||||
icon: Icons.edit,
|
icon: Icons.edit,
|
||||||
label: 'Éditer le profil',
|
label: 'Éditer le profil',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Naviguer vers la page d'édition de profil
|
print("Édition du profil.");
|
||||||
|
// Logique de navigation vers l'édition du profil
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_buildAnimatedListTile(
|
_buildAnimatedListTile(
|
||||||
icon: Icons.camera_alt,
|
icon: Icons.camera_alt,
|
||||||
label: 'Changer la photo de profil',
|
label: 'Changer la photo de profil',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Naviguer vers la page de changement de photo de profil
|
print("Changement de la photo de profil.");
|
||||||
|
// Logique de changement de la photo de profil
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_buildAnimatedListTile(
|
_buildAnimatedListTile(
|
||||||
icon: Icons.lock,
|
icon: Icons.lock,
|
||||||
label: 'Changer le mot de passe',
|
label: 'Changer le mot de passe',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Naviguer vers la page de changement de mot de passe
|
print("Changement du mot de passe.");
|
||||||
|
// Logique de changement de mot de passe
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -184,8 +218,9 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildStatisticsSectionCard() {
|
Widget _buildStatisticsSectionCard() {
|
||||||
return Card(
|
return Card(
|
||||||
color: const Color(0xFF292B37),
|
color: AppColors.cardColor,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
|
elevation: 2,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -232,8 +267,9 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
required List<Widget> children,
|
required List<Widget> children,
|
||||||
}) {
|
}) {
|
||||||
return Card(
|
return Card(
|
||||||
color: const Color(0xFF292B37),
|
color: AppColors.cardColor,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
|
elevation: 2,
|
||||||
child: ExpansionTile(
|
child: ExpansionTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
title,
|
title,
|
||||||
@@ -243,9 +279,9 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: Icon(icon, color: const Color(0xFF1DBF73)),
|
leading: Icon(icon, color: AppColors.accentColor),
|
||||||
iconColor: const Color(0xFF1DBF73),
|
iconColor: AppColors.accentColor,
|
||||||
collapsedIconColor: const Color(0xFF1DBF73),
|
collapsedIconColor: AppColors.accentColor,
|
||||||
children: children,
|
children: children,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -253,8 +289,9 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildSupportSectionCard() {
|
Widget _buildSupportSectionCard() {
|
||||||
return Card(
|
return Card(
|
||||||
color: const Color(0xFF292B37),
|
color: AppColors.cardColor,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
|
elevation: 2,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const Padding(
|
const Padding(
|
||||||
@@ -272,21 +309,24 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
icon: Icons.help,
|
icon: Icons.help,
|
||||||
label: 'Support et Assistance',
|
label: 'Support et Assistance',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Naviguer vers la page de support
|
print("Accès au Support et Assistance.");
|
||||||
|
// Logique de navigation vers le support
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_buildAnimatedListTile(
|
_buildAnimatedListTile(
|
||||||
icon: Icons.article,
|
icon: Icons.article,
|
||||||
label: 'Conditions d\'utilisation',
|
label: 'Conditions d\'utilisation',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Naviguer vers les conditions d'utilisation
|
print("Accès aux conditions d'utilisation.");
|
||||||
|
// Logique de navigation vers les conditions d'utilisation
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_buildAnimatedListTile(
|
_buildAnimatedListTile(
|
||||||
icon: Icons.privacy_tip,
|
icon: Icons.privacy_tip,
|
||||||
label: 'Politique de confidentialité',
|
label: 'Politique de confidentialité',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Naviguer vers la politique de confidentialité
|
print("Accès à la politique de confidentialité.");
|
||||||
|
// Logique de navigation vers la politique de confidentialité
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -296,8 +336,9 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildAccountDeletionCard(BuildContext context) {
|
Widget _buildAccountDeletionCard(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
color: const Color(0xFF292B37),
|
color: AppColors.cardColor,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
|
elevation: 2,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(Icons.delete, color: Colors.redAccent),
|
leading: const Icon(Icons.delete, color: Colors.redAccent),
|
||||||
title: const Text(
|
title: const Text(
|
||||||
@@ -307,7 +348,6 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
_showDeleteConfirmationDialog(context);
|
_showDeleteConfirmationDialog(context);
|
||||||
},
|
},
|
||||||
hoverColor: Colors.red.withOpacity(0.1),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -317,7 +357,7 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
backgroundColor: const Color(0xFF1E1E2C),
|
backgroundColor: AppColors.backgroundColor,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Confirmer la suppression',
|
'Confirmer la suppression',
|
||||||
style: TextStyle(color: Colors.white),
|
style: TextStyle(color: Colors.white),
|
||||||
@@ -329,17 +369,17 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop(); // Fermer le popup
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: Text(
|
||||||
'Annuler',
|
'Annuler',
|
||||||
style: TextStyle(color: Color(0xFF1DBF73)),
|
style: TextStyle(color: AppColors.accentColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Logique de suppression du compte ici
|
print("Suppression du compte confirmée.");
|
||||||
Navigator.of(context).pop(); // Fermer le popup après la suppression
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Supprimer',
|
'Supprimer',
|
||||||
@@ -362,12 +402,11 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
splashColor: Colors.blueAccent.withOpacity(0.2),
|
splashColor: Colors.blueAccent.withOpacity(0.2),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Icon(icon, color: const Color(0xFF1DBF73)),
|
leading: Icon(icon, color: AppColors.accentColor),
|
||||||
title: Text(
|
title: Text(
|
||||||
label,
|
label,
|
||||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600),
|
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
hoverColor: Colors.blue.withOpacity(0.1),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -378,7 +417,7 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
required String value,
|
required String value,
|
||||||
}) {
|
}) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Icon(icon, color: const Color(0xFF1DBF73)),
|
leading: Icon(icon, color: AppColors.accentColor),
|
||||||
title: Text(label, style: const TextStyle(color: Colors.white)),
|
title: Text(label, style: const TextStyle(color: Colors.white)),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
value,
|
value,
|
||||||
@@ -388,7 +427,6 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
hoverColor: Colors.blue.withOpacity(0.1),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
396
lib/presentation/screens/signup/SignUpScreen.dart
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:afterwork/data/datasources/user_remote_data_source.dart';
|
||||||
|
import 'package:afterwork/data/models/user_model.dart';
|
||||||
|
import 'package:afterwork/data/services/preferences_helper.dart';
|
||||||
|
import 'package:afterwork/data/services/secure_storage.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:loading_icon_button/loading_icon_button.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../../../core/theme/theme_provider.dart';
|
||||||
|
|
||||||
|
/// Écran d'inscription pour l'application AfterWork.
|
||||||
|
/// Permet à l'utilisateur de créer un nouveau compte avec des champs comme nom, prénom, email, mot de passe.
|
||||||
|
class SignUpScreen extends StatefulWidget {
|
||||||
|
const SignUpScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SignUpScreenState createState() => _SignUpScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SignUpScreenState extends State<SignUpScreen> {
|
||||||
|
final _formKey = GlobalKey<FormState>(); // Clé pour valider le formulaire
|
||||||
|
|
||||||
|
// Champs utilisateur
|
||||||
|
String _nom = ''; // Nom de l'utilisateur
|
||||||
|
String _prenoms = ''; // Prénom de l'utilisateur
|
||||||
|
String _email = ''; // Email de l'utilisateur
|
||||||
|
String _password = ''; // Mot de passe de l'utilisateur
|
||||||
|
String _confirmPassword = ''; // Confirmation du mot de passe
|
||||||
|
|
||||||
|
// États de gestion
|
||||||
|
bool _isPasswordVisible = false; // Pour afficher/masquer le mot de passe
|
||||||
|
bool _isSubmitting = false; // Indicateur pour l'état de soumission du formulaire
|
||||||
|
bool _showErrorMessage = false; // Affichage des erreurs
|
||||||
|
|
||||||
|
// Services pour les opérations
|
||||||
|
final UserRemoteDataSource _userRemoteDataSource = UserRemoteDataSource(http.Client());
|
||||||
|
final SecureStorage _secureStorage = SecureStorage();
|
||||||
|
final PreferencesHelper _preferencesHelper = PreferencesHelper();
|
||||||
|
|
||||||
|
// Contrôleur pour le bouton de chargement
|
||||||
|
final _btnController = LoadingButtonController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_btnController.reset();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fonction pour basculer la visibilité du mot de passe
|
||||||
|
void _togglePasswordVisibility() {
|
||||||
|
setState(() {
|
||||||
|
_isPasswordVisible = !_isPasswordVisible;
|
||||||
|
});
|
||||||
|
print("Visibilité du mot de passe basculée: $_isPasswordVisible");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
print("Tentative de soumission du formulaire d'inscription.");
|
||||||
|
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
setState(() {
|
||||||
|
_isSubmitting = true;
|
||||||
|
_showErrorMessage = false;
|
||||||
|
});
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
|
||||||
|
// Vérifier si le mot de passe et la confirmation correspondent
|
||||||
|
if (_password != _confirmPassword) {
|
||||||
|
setState(() {
|
||||||
|
_showErrorMessage = true;
|
||||||
|
});
|
||||||
|
print("Les mots de passe ne correspondent pas.");
|
||||||
|
_btnController.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_btnController.start();
|
||||||
|
|
||||||
|
// Créer l'utilisateur avec les informations fournies
|
||||||
|
final UserModel user = UserModel(
|
||||||
|
userId: '', // L'ID sera généré côté serveur
|
||||||
|
nom: _nom,
|
||||||
|
prenoms: _prenoms,
|
||||||
|
email: _email,
|
||||||
|
motDePasse: _password, // Le mot de passe sera envoyé en clair pour l'instant
|
||||||
|
);
|
||||||
|
|
||||||
|
// Envoi des informations pour créer un nouvel utilisateur
|
||||||
|
final createdUser = await _userRemoteDataSource.createUser(user);
|
||||||
|
if (createdUser == null) {
|
||||||
|
throw Exception("La création du compte a échoué.");
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Utilisateur créé : ${createdUser.userId}");
|
||||||
|
|
||||||
|
// Sauvegarder les informations de l'utilisateur
|
||||||
|
await _secureStorage.saveUserId(createdUser.userId);
|
||||||
|
await _preferencesHelper.saveUserName(createdUser.nom);
|
||||||
|
await _preferencesHelper.saveUserLastName(createdUser.prenoms);
|
||||||
|
|
||||||
|
// Rediriger vers la page d'accueil ou une page de confirmation
|
||||||
|
Navigator.pushReplacementNamed(context, '/home');
|
||||||
|
} catch (e) {
|
||||||
|
print("Erreur lors de la création du compte : $e");
|
||||||
|
_btnController.error();
|
||||||
|
setState(() {
|
||||||
|
_showErrorMessage = true;
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
_btnController.reset();
|
||||||
|
setState(() {
|
||||||
|
_isSubmitting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("Échec de validation du formulaire.");
|
||||||
|
_btnController.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context); // Utilisation du thème global
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
final themeProvider = Provider.of<ThemeProvider>(context);
|
||||||
|
|
||||||
|
// Vérification si le clavier est visible
|
||||||
|
bool isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom != 0;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
// Arrière-plan animé avec un dégradé basé sur le thème
|
||||||
|
AnimatedContainer(
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
theme.colorScheme.primary,
|
||||||
|
theme.colorScheme.secondary
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_isSubmitting)
|
||||||
|
const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
// Bouton pour basculer entre les modes jour et nuit
|
||||||
|
Positioned(
|
||||||
|
top: 40,
|
||||||
|
right: 20,
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
themeProvider.isDarkMode ? Icons.dark_mode : Icons.light_mode,
|
||||||
|
color: theme.iconTheme.color,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
themeProvider.toggleTheme();
|
||||||
|
print("Thème basculé : ${themeProvider.isDarkMode ? 'Sombre' : 'Clair'}");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Formulaire d'inscription
|
||||||
|
Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Logo de l'application
|
||||||
|
Image.asset(
|
||||||
|
'lib/assets/images/logo.png',
|
||||||
|
height: size.height * 0.25,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// Titre de la page
|
||||||
|
Text(
|
||||||
|
'Créer un compte AfterWork',
|
||||||
|
style: theme.textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
// Champ nom
|
||||||
|
_buildTextFormField(
|
||||||
|
label: 'Nom',
|
||||||
|
icon: Icons.person,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Veuillez entrer votre nom';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (value) {
|
||||||
|
_nom = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// Champ prénom
|
||||||
|
_buildTextFormField(
|
||||||
|
label: 'Prénoms',
|
||||||
|
icon: Icons.person_outline,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Veuillez entrer votre prénom';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (value) {
|
||||||
|
_prenoms = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// Champ email
|
||||||
|
_buildTextFormField(
|
||||||
|
label: 'Email',
|
||||||
|
icon: Icons.email,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Veuillez entrer votre email';
|
||||||
|
}
|
||||||
|
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
|
||||||
|
return 'Veuillez entrer un email valide';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (value) {
|
||||||
|
_email = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// Champ mot de passe
|
||||||
|
_buildTextFormField(
|
||||||
|
label: 'Mot de passe',
|
||||||
|
icon: Icons.lock,
|
||||||
|
obscureText: !_isPasswordVisible,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_isPasswordVisible
|
||||||
|
? Icons.visibility
|
||||||
|
: Icons.visibility_off,
|
||||||
|
color: theme.iconTheme.color,
|
||||||
|
),
|
||||||
|
onPressed: _togglePasswordVisibility,
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Veuillez entrer votre mot de passe';
|
||||||
|
}
|
||||||
|
if (value.length < 6) {
|
||||||
|
return 'Le mot de passe doit comporter au moins 6 caractères';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (value) {
|
||||||
|
_password = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// Champ de confirmation du mot de passe
|
||||||
|
_buildTextFormField(
|
||||||
|
label: 'Confirmer le mot de passe',
|
||||||
|
icon: Icons.lock_outline,
|
||||||
|
obscureText: !_isPasswordVisible,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_isPasswordVisible
|
||||||
|
? Icons.visibility
|
||||||
|
: Icons.visibility_off,
|
||||||
|
color: theme.iconTheme.color,
|
||||||
|
),
|
||||||
|
onPressed: _togglePasswordVisibility,
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Veuillez confirmer votre mot de passe';
|
||||||
|
}
|
||||||
|
if (value != _password) {
|
||||||
|
return 'Les mots de passe ne correspondent pas';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (value) {
|
||||||
|
_confirmPassword = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
// Bouton de création de compte
|
||||||
|
SizedBox(
|
||||||
|
width: size.width * 0.85,
|
||||||
|
child: LoadingButton(
|
||||||
|
controller: _btnController,
|
||||||
|
onPressed: _isSubmitting ? null : _submit,
|
||||||
|
iconData: Icons.person_add,
|
||||||
|
iconColor: theme.colorScheme.onPrimary,
|
||||||
|
child: Text(
|
||||||
|
'Créer un compte',
|
||||||
|
style: theme.textTheme.bodyLarge!.copyWith(
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// Lien pour revenir à la connexion
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'Déjà un compte ? Connectez-vous',
|
||||||
|
style: theme.textTheme.bodyMedium!
|
||||||
|
.copyWith(color: Colors.white70),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Affichage du message d'erreur si nécessaire
|
||||||
|
if (_showErrorMessage)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(top: 20),
|
||||||
|
child: Text(
|
||||||
|
'Erreur lors de la création du compte. Veuillez vérifier vos informations.',
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 16),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Pied de page avec logo et mention copyright
|
||||||
|
AnimatedPositioned(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
bottom: isKeyboardVisible ? 0 : 20,
|
||||||
|
left: isKeyboardVisible ? 20 : 0,
|
||||||
|
right: isKeyboardVisible ? 20 : 0,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: isKeyboardVisible
|
||||||
|
? MainAxisAlignment.spaceBetween
|
||||||
|
: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'lib/assets/images/logolionsdev.png',
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
if (isKeyboardVisible)
|
||||||
|
Text(
|
||||||
|
'© 2024 LionsDev',
|
||||||
|
style: theme.textTheme.bodyMedium!
|
||||||
|
.copyWith(color: Colors.white70),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Widget réutilisable pour les champs de texte avec validation et design amélioré
|
||||||
|
Widget _buildTextFormField({
|
||||||
|
required String label,
|
||||||
|
required IconData icon,
|
||||||
|
bool obscureText = false,
|
||||||
|
Widget? suffixIcon,
|
||||||
|
required FormFieldValidator<String> validator,
|
||||||
|
required FormFieldSetter<String> onSaved,
|
||||||
|
}) {
|
||||||
|
final theme = Theme.of(context); // Utilisation du thème global
|
||||||
|
|
||||||
|
return TextFormField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: label,
|
||||||
|
filled: true,
|
||||||
|
fillColor: theme.inputDecorationTheme.fillColor,
|
||||||
|
labelStyle: theme.textTheme.bodyMedium,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(icon, color: theme.iconTheme.color),
|
||||||
|
suffixIcon: suffixIcon,
|
||||||
|
),
|
||||||
|
obscureText: obscureText,
|
||||||
|
style: theme.textTheme.bodyLarge,
|
||||||
|
validator: validator,
|
||||||
|
onSaved: onSaved,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,10 @@ import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
|||||||
@immutable
|
@immutable
|
||||||
abstract class EventEvent {}
|
abstract class EventEvent {}
|
||||||
|
|
||||||
class LoadEvents extends EventEvent {}
|
class LoadEvents extends EventEvent {
|
||||||
|
final String userId;
|
||||||
|
LoadEvents(this.userId);
|
||||||
|
}
|
||||||
|
|
||||||
class AddEvent extends EventEvent {
|
class AddEvent extends EventEvent {
|
||||||
final EventModel event;
|
final EventModel event;
|
||||||
@@ -15,6 +18,18 @@ class AddEvent extends EventEvent {
|
|||||||
AddEvent(this.event);
|
AddEvent(this.event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CloseEvent extends EventEvent {
|
||||||
|
final String eventId;
|
||||||
|
|
||||||
|
CloseEvent(this.eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReopenEvent extends EventEvent {
|
||||||
|
final String eventId;
|
||||||
|
|
||||||
|
ReopenEvent(this.eventId);
|
||||||
|
}
|
||||||
|
|
||||||
// Déclaration des états
|
// Déclaration des états
|
||||||
@immutable
|
@immutable
|
||||||
abstract class EventState {}
|
abstract class EventState {}
|
||||||
@@ -35,39 +50,61 @@ class EventError extends EventState {
|
|||||||
EventError(this.message);
|
EventError(this.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bloc principal pour gérer la logique des événements
|
// Bloc pour la gestion des événements
|
||||||
class EventBloc extends Bloc<EventEvent, EventState> {
|
class EventBloc extends Bloc<EventEvent, EventState> {
|
||||||
final EventRemoteDataSource remoteDataSource;
|
final EventRemoteDataSource remoteDataSource;
|
||||||
|
|
||||||
EventBloc({required this.remoteDataSource}) : super(EventInitial()) {
|
EventBloc({required this.remoteDataSource}) : super(EventInitial()) {
|
||||||
on<LoadEvents>(_onLoadEvents);
|
on<LoadEvents>(_onLoadEvents);
|
||||||
on<AddEvent>(_onAddEvent);
|
on<AddEvent>(_onAddEvent);
|
||||||
|
on<CloseEvent>(_onCloseEvent);
|
||||||
|
on<ReopenEvent>(_onReopenEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gestion de l'événement LoadEvents
|
// Gestion du chargement des événements
|
||||||
Future<void> _onLoadEvents(LoadEvents event, Emitter<EventState> emit) async {
|
Future<void> _onLoadEvents(LoadEvents event, Emitter<EventState> emit) async {
|
||||||
emit(EventLoading());
|
emit(EventLoading());
|
||||||
try {
|
try {
|
||||||
final events = await remoteDataSource.getAllEvents();
|
final events = await remoteDataSource.getAllEvents();
|
||||||
emit(EventLoaded(events));
|
emit(EventLoaded(events));
|
||||||
print('Événements chargés avec succès.');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(EventError('Erreur lors du chargement des événements.'));
|
emit(EventError('Erreur lors du chargement des événements.'));
|
||||||
print('Erreur lors du chargement des événements: $e');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gestion de l'événement AddEvent
|
// Gestion de l'ajout d'un nouvel événement
|
||||||
Future<void> _onAddEvent(AddEvent event, Emitter<EventState> emit) async {
|
Future<void> _onAddEvent(AddEvent event, Emitter<EventState> emit) async {
|
||||||
emit(EventLoading());
|
emit(EventLoading());
|
||||||
try {
|
try {
|
||||||
await remoteDataSource.createEvent(event.event);
|
await remoteDataSource.createEvent(event.event);
|
||||||
final events = await remoteDataSource.getAllEvents();
|
final events = await remoteDataSource.getAllEvents();
|
||||||
emit(EventLoaded(events));
|
emit(EventLoaded(events));
|
||||||
print('Événement ajouté avec succès.');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(EventError('Erreur lors de l\'ajout de l\'événement.'));
|
emit(EventError('Erreur lors de l\'ajout de l\'événement.'));
|
||||||
print('Erreur lors de l\'ajout de l\'événement: $e');
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestion de la fermeture d'un événement
|
||||||
|
Future<void> _onCloseEvent(CloseEvent event, Emitter<EventState> emit) async {
|
||||||
|
emit(EventLoading());
|
||||||
|
try {
|
||||||
|
await remoteDataSource.closeEvent(event.eventId);
|
||||||
|
final events = await remoteDataSource.getAllEvents();
|
||||||
|
emit(EventLoaded(events));
|
||||||
|
} catch (e) {
|
||||||
|
emit(EventError('Erreur lors de la fermeture de l\'événement.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestion de la réouverture d'un événement
|
||||||
|
Future<void> _onReopenEvent(ReopenEvent event, Emitter<EventState> emit) async {
|
||||||
|
emit(EventLoading());
|
||||||
|
try {
|
||||||
|
await remoteDataSource.reopenEvent(event.eventId);
|
||||||
|
final events = await remoteDataSource.getAllEvents();
|
||||||
|
emit(EventLoaded(events));
|
||||||
|
} catch (e) {
|
||||||
|
emit(EventError('Erreur lors de la réouverture de l\'événement.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,42 +2,52 @@ import 'package:afterwork/domain/entities/user.dart';
|
|||||||
import 'package:afterwork/domain/usecases/get_user.dart';
|
import 'package:afterwork/domain/usecases/get_user.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
/// Bloc pour la gestion des événements et états liés à l'utilisateur.
|
||||||
class UserBloc extends Bloc<UserEvent, UserState> {
|
class UserBloc extends Bloc<UserEvent, UserState> {
|
||||||
final GetUser getUser;
|
final GetUser getUser;
|
||||||
|
|
||||||
|
/// Constructeur avec injection du cas d'utilisation `GetUser`.
|
||||||
UserBloc({required this.getUser}) : super(UserInitial());
|
UserBloc({required this.getUser}) : super(UserInitial());
|
||||||
|
|
||||||
|
@override
|
||||||
Stream<UserState> mapEventToState(UserEvent event) async* {
|
Stream<UserState> mapEventToState(UserEvent event) async* {
|
||||||
if (event is GetUserById) {
|
if (event is GetUserById) {
|
||||||
yield UserLoading();
|
yield UserLoading();
|
||||||
final either = await getUser(event.id);
|
final either = await getUser(event.id);
|
||||||
|
|
||||||
yield either.fold(
|
yield either.fold(
|
||||||
(failure) => UserError(),
|
(failure) => UserError(),
|
||||||
(user) => UserLoaded(user: user),
|
(user) => UserLoaded(user: user),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Classe abstraite représentant les événements liés à l'utilisateur.
|
||||||
abstract class UserEvent {}
|
abstract class UserEvent {}
|
||||||
|
|
||||||
|
/// Événement pour récupérer un utilisateur par son ID.
|
||||||
class GetUserById extends UserEvent {
|
class GetUserById extends UserEvent {
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
GetUserById(this.id);
|
GetUserById(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Classe abstraite représentant les états possibles du BLoC utilisateur.
|
||||||
abstract class UserState {}
|
abstract class UserState {}
|
||||||
|
|
||||||
|
/// État initial lorsque rien n'est encore chargé.
|
||||||
class UserInitial extends UserState {}
|
class UserInitial extends UserState {}
|
||||||
|
|
||||||
|
/// État indiquant que les données utilisateur sont en cours de chargement.
|
||||||
class UserLoading extends UserState {}
|
class UserLoading extends UserState {}
|
||||||
|
|
||||||
|
/// État indiquant que les données utilisateur ont été chargées avec succès.
|
||||||
class UserLoaded extends UserState {
|
class UserLoaded extends UserState {
|
||||||
final User user;
|
final User user;
|
||||||
|
|
||||||
UserLoaded({required this.user});
|
UserLoaded({required this.user});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// État indiquant qu'une erreur est survenue lors de la récupération des données utilisateur.
|
||||||
class UserError extends UserState {}
|
class UserError extends UserState {}
|
||||||
|
|||||||
23
lib/presentation/widgets/animated_action_button.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AnimatedActionButton extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
const AnimatedActionButton({
|
||||||
|
super.key,
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: Colors.white, size: 30),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(label, style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
lib/presentation/widgets/create_story.dart
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:camerawesome/camerawesome_plugin.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import '../../core/constants/colors.dart';
|
||||||
|
|
||||||
|
class CreateStoryPage extends StatefulWidget {
|
||||||
|
const CreateStoryPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CreateStoryPageState createState() => _CreateStoryPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateStoryPageState extends State<CreateStoryPage> {
|
||||||
|
final Logger logger = Logger();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
extendBodyBehindAppBar: true, // Permet à l'AppBar de passer en mode transparent
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Créer une nouvelle story'),
|
||||||
|
backgroundColor: Colors.transparent, // Transparence
|
||||||
|
elevation: 0, // Pas d'ombre pour l'en-tête
|
||||||
|
leading: IconButton(
|
||||||
|
icon: Icon(Icons.arrow_back, color: AppColors.onPrimary), // Couleur adaptative
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Bouton retour
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
CameraAwesomeBuilder.awesome(
|
||||||
|
saveConfig: SaveConfig.photoAndVideo(
|
||||||
|
photoPathBuilder: (sensors) async {
|
||||||
|
final sensor = sensors.first; // Utilisation du premier capteur
|
||||||
|
final Directory extDir = await getTemporaryDirectory();
|
||||||
|
final Directory testDir = await Directory('${extDir.path}/camerawesome').create(recursive: true);
|
||||||
|
final String filePath = '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg';
|
||||||
|
return SingleCaptureRequest(filePath, sensor); // CaptureRequest pour la photo
|
||||||
|
},
|
||||||
|
videoPathBuilder: (sensors) async {
|
||||||
|
final sensor = sensors.first; // Utilisation du premier capteur
|
||||||
|
final Directory extDir = await getTemporaryDirectory();
|
||||||
|
final Directory testDir = await Directory('${extDir.path}/camerawesome').create(recursive: true);
|
||||||
|
final String filePath = '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.mp4';
|
||||||
|
return SingleCaptureRequest(filePath, sensor); // CaptureRequest pour la vidéo
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sensorConfig: SensorConfig.single(
|
||||||
|
sensor: Sensor.position(SensorPosition.back), // Configuration correcte du capteur
|
||||||
|
),
|
||||||
|
onMediaTap: (mediaCapture) async {
|
||||||
|
final captureRequest = mediaCapture.captureRequest;
|
||||||
|
|
||||||
|
if (captureRequest is SingleCaptureRequest) {
|
||||||
|
final filePath = captureRequest.path; // Accès au chemin de fichier
|
||||||
|
if (filePath != null) {
|
||||||
|
logger.i('Média capturé : $filePath');
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
content: Text('Média sauvegardé à $filePath'),
|
||||||
|
backgroundColor: AppColors.accentColor, // Couleur adaptative du snack bar
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
logger.e('Erreur : Aucun fichier capturé.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.e('Erreur : Capture non reconnue.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ class CustomDrawer extends StatelessWidget {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
DrawerHeader(
|
const DrawerHeader(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.blueAccent,
|
color: Colors.blueAccent,
|
||||||
),
|
),
|
||||||
|
|||||||
58
lib/presentation/widgets/event_list.dart
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:afterwork/data/models/event_model.dart';
|
||||||
|
|
||||||
|
import '../screens/event/event_card.dart';
|
||||||
|
|
||||||
|
class EventList extends StatelessWidget {
|
||||||
|
final List<EventModel> events;
|
||||||
|
|
||||||
|
const EventList({Key? key, required this.events}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: events.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final event = events[index];
|
||||||
|
|
||||||
|
return EventCard(
|
||||||
|
event: event,
|
||||||
|
userId: 'user_id_here', // Vous pouvez passer l'ID réel de l'utilisateur connecté
|
||||||
|
userName: 'John', // Vous pouvez passer le prénom réel de l'utilisateur
|
||||||
|
userLastName: 'Doe', // Vous pouvez passer le nom réel de l'utilisateur
|
||||||
|
onReact: () => _handleReact(event),
|
||||||
|
onComment: () => _handleComment(event),
|
||||||
|
onShare: () => _handleShare(event),
|
||||||
|
onParticipate: () => _handleParticipate(event),
|
||||||
|
onCloseEvent: () => _handleCloseEvent(event),
|
||||||
|
onReopenEvent: () => _handleReopenEvent(event),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestion des actions
|
||||||
|
void _handleReact(EventModel event) {
|
||||||
|
print('Réaction ajoutée à l\'événement ${event.title}');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleComment(EventModel event) {
|
||||||
|
print('Commentaire ajouté à l\'événement ${event.title}');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleShare(EventModel event) {
|
||||||
|
print('Événement partagé : ${event.title}');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleParticipate(EventModel event) {
|
||||||
|
print('Participation confirmée à l\'événement ${event.title}');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleCloseEvent(EventModel event) {
|
||||||
|
print('Événement ${event.title} fermé');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleReopenEvent(EventModel event) {
|
||||||
|
print('Événement ${event.title} réouvert');
|
||||||
|
}
|
||||||
|
}
|
||||||
62
lib/presentation/widgets/friend_suggestions.dart
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class FriendSuggestions extends StatelessWidget {
|
||||||
|
final Size size;
|
||||||
|
|
||||||
|
const FriendSuggestions({required this.size, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: List.generate(3, (index) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 20),
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
width: size.width,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[850],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const CircleAvatar(
|
||||||
|
radius: 30,
|
||||||
|
backgroundImage: AssetImage('lib/assets/images/friend_placeholder.png'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Nom d\'utilisateur',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
print('Ajouter comme ami');
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.teal,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('Ajouter'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
lib/presentation/widgets/group_list.dart
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class GroupList extends StatelessWidget {
|
||||||
|
final Size size;
|
||||||
|
|
||||||
|
const GroupList({required this.size, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: List.generate(3, (index) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 20),
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
width: size.width,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[850],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const CircleAvatar(
|
||||||
|
radius: 30,
|
||||||
|
backgroundImage: AssetImage('lib/assets/images/group_placeholder.png'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Club des Amateurs de Cinéma',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
print('Rejoindre le groupe');
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blueAccent,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('Rejoindre'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
lib/presentation/widgets/popular_activity_list.dart
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PopularActivityList extends StatelessWidget {
|
||||||
|
final Size size;
|
||||||
|
|
||||||
|
const PopularActivityList({required this.size, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: ListView.separated(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: 3,
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(width: 15),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Container(
|
||||||
|
width: size.width * 0.8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[800],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
image: const DecorationImage(
|
||||||
|
image: AssetImage('lib/assets/images/activity_placeholder.png'),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
colorFilter: ColorFilter.mode(Colors.black38, BlendMode.darken),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.all(12.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Soirée Stand-up Comedy',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
'Vendredi, 20h00',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
lib/presentation/widgets/quick_action_button.dart
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class QuickActionButton extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final IconData icon;
|
||||||
|
final Color color;
|
||||||
|
final double fontSize; // Ajout d'un paramètre pour personnaliser la taille du texte
|
||||||
|
|
||||||
|
const QuickActionButton({
|
||||||
|
required this.label,
|
||||||
|
required this.icon,
|
||||||
|
required this.color,
|
||||||
|
this.fontSize = 14, // Valeur par défaut
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 30,
|
||||||
|
backgroundColor: color.withOpacity(0.2),
|
||||||
|
child: Icon(icon, color: color, size: 28),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: fontSize), // Utilisation de fontSize
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
lib/presentation/widgets/recommended_event_list.dart
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RecommendedEventList extends StatelessWidget {
|
||||||
|
final Size size;
|
||||||
|
|
||||||
|
const RecommendedEventList({required this.size, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 240,
|
||||||
|
child: ListView.separated(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: 3, // Nombre d'événements fictifs
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(width: 15),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
width: size.width * 0.8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[800],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
image: const DecorationImage(
|
||||||
|
image: AssetImage('lib/assets/images/event_placeholder.png'),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
colorFilter: ColorFilter.mode(Colors.black38, BlendMode.darken),
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.all(12.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Concert de Jazz',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
'Samedi, 18h00',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
lib/presentation/widgets/section_header.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SectionHeader extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final IconData icon;
|
||||||
|
final TextStyle? textStyle; // Ajout de la possibilité de personnaliser le style du texte
|
||||||
|
|
||||||
|
const SectionHeader({
|
||||||
|
required this.title,
|
||||||
|
required this.icon,
|
||||||
|
this.textStyle, // Paramètre optionnel
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: textStyle ?? const TextStyle( // Utilisation du style fourni ou d'un style par défaut
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(icon, color: Colors.white),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
112
lib/presentation/widgets/story_detail.dart
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:afterwork/presentation/widgets/story_video_player.dart';
|
||||||
|
import '../../../core/utils/calculate_time_ago.dart';
|
||||||
|
import 'animated_action_button.dart';
|
||||||
|
|
||||||
|
class StoryDetail extends StatefulWidget {
|
||||||
|
final String username;
|
||||||
|
final DateTime publicationDate;
|
||||||
|
final String mediaUrl;
|
||||||
|
final String userImage;
|
||||||
|
final bool isVideo;
|
||||||
|
|
||||||
|
const StoryDetail({
|
||||||
|
super.key,
|
||||||
|
required this.username,
|
||||||
|
required this.publicationDate,
|
||||||
|
required this.mediaUrl,
|
||||||
|
required this.userImage,
|
||||||
|
required this.isVideo,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
StoryDetailState createState() => StoryDetailState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class StoryDetailState extends State<StoryDetail> {
|
||||||
|
late Offset _startDragOffset;
|
||||||
|
late Offset _currentDragOffset;
|
||||||
|
bool _isDragging = false;
|
||||||
|
|
||||||
|
// Gestion du swipe vertical pour fermer la story
|
||||||
|
void _onVerticalDragStart(DragStartDetails details) {
|
||||||
|
_startDragOffset = details.globalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onVerticalDragUpdate(DragUpdateDetails details) {
|
||||||
|
_currentDragOffset = details.globalPosition;
|
||||||
|
if (_currentDragOffset.dy - _startDragOffset.dy > 100) {
|
||||||
|
setState(() {
|
||||||
|
_isDragging = true;
|
||||||
|
});
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
iconTheme: const IconThemeData(color: Colors.white),
|
||||||
|
),
|
||||||
|
body: GestureDetector(
|
||||||
|
onVerticalDragStart: _onVerticalDragStart,
|
||||||
|
onVerticalDragUpdate: _onVerticalDragUpdate,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: _isDragging ? 0.5 : 1.0,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: widget.isVideo
|
||||||
|
? StoryVideoPlayer(mediaUrl: widget.mediaUrl)
|
||||||
|
: Image.asset(widget.mediaUrl, fit: BoxFit.cover),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Informations sur l'utilisateur
|
||||||
|
Positioned(
|
||||||
|
top: 40,
|
||||||
|
left: 20,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(radius: 32, backgroundImage: AssetImage(widget.userImage)),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.username,
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Il y a ${calculateTimeAgo(widget.publicationDate)}',
|
||||||
|
style: const TextStyle(color: Colors.white70, fontSize: 14),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Boutons d'actions flottants à droite
|
||||||
|
const Positioned(
|
||||||
|
right: 20,
|
||||||
|
bottom: 100,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
AnimatedActionButton(icon: Icons.favorite_border, label: 'J\'aime'),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
AnimatedActionButton(icon: Icons.comment, label: 'Commenter'),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
AnimatedActionButton(icon: Icons.share, label: 'Partager'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
194
lib/presentation/widgets/story_section.dart
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
import '../../core/constants/colors.dart';
|
||||||
|
import '../../core/utils/calculate_time_ago.dart';
|
||||||
|
import 'story_detail.dart';
|
||||||
|
import 'create_story.dart';
|
||||||
|
|
||||||
|
/// La classe StorySection représente la section des stories dans l'interface.
|
||||||
|
/// Elle affiche une liste horizontale de stories et permet à l'utilisateur d'ajouter une nouvelle story.
|
||||||
|
/// Les logs sont utilisés pour tracer chaque action réalisée dans l'interface.
|
||||||
|
class StorySection extends StatelessWidget {
|
||||||
|
final Size size;
|
||||||
|
final Logger logger = Logger(); // Logger pour tracer les événements et actions
|
||||||
|
|
||||||
|
StorySection({required this.size, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
logger.i('Construction de la section des stories');
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: SizedBox(
|
||||||
|
height: size.height / 4.5, // Hauteur ajustée pour éviter le débordement
|
||||||
|
child: ListView.separated(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: 6, // Nombre de stories à afficher
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(width: 8),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == 0) return _buildAddStoryCard(context);
|
||||||
|
|
||||||
|
DateTime publicationDate = DateTime.now().subtract(Duration(hours: (index - 1) * 6));
|
||||||
|
logger.i('Affichage de la story $index avec la date $publicationDate');
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
logger.i('Clic sur la story $index, affichage du détail');
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
PageRouteBuilder(
|
||||||
|
pageBuilder: (context, animation, secondaryAnimation) => FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: StoryDetail(
|
||||||
|
username: 'Utilisateur ${index - 1}',
|
||||||
|
publicationDate: publicationDate,
|
||||||
|
mediaUrl: 'https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4',
|
||||||
|
userImage: 'lib/assets/images/user_placeholder.png',
|
||||||
|
isVideo: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: _buildStoryCard(index, publicationDate),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construit une carte de story à partir de l'index et de la date de publication.
|
||||||
|
Widget _buildStoryCard(int index, DateTime publicationDate) {
|
||||||
|
return Column( // Utilisation de Column sans Expanded pour éviter les erreurs
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: size.width / 4.5,
|
||||||
|
height: size.height / 5.5, // Hauteur ajustée pour éviter le dépassement
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.cardColor, // Utilisation des couleurs automatiques pour le fond des cartes
|
||||||
|
borderRadius: BorderRadius.circular(20), // Bords arrondis à 20 pixels
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Positioned.fill(child: _buildGradientOverlay()),
|
||||||
|
Positioned(top: 6, right: 6, child: _buildAvatar(index)),
|
||||||
|
Positioned(bottom: 10, left: 10, child: _buildUsername(index)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4), // Espace entre la carte et la date
|
||||||
|
_buildPublicationDate(publicationDate),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construit un overlay en dégradé pour la story.
|
||||||
|
Widget _buildGradientOverlay() {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.black.withOpacity(0.4), Colors.transparent],
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(20), // Assure que l'overlay suit les bords arrondis
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construit l'avatar de l'utilisateur pour la story.
|
||||||
|
Widget _buildAvatar(int index) {
|
||||||
|
return Hero(
|
||||||
|
tag: 'avatar-$index',
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 16,
|
||||||
|
backgroundColor: Colors.grey.withOpacity(0.2),
|
||||||
|
child: const CircleAvatar(
|
||||||
|
radius: 14,
|
||||||
|
backgroundImage: AssetImage('lib/assets/images/user_placeholder.png'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construit le nom d'utilisateur affiché sous la story.
|
||||||
|
Widget _buildUsername(int index) {
|
||||||
|
return Text(
|
||||||
|
'Utilisateur ${index - 1}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Montserrat',
|
||||||
|
color: AppColors.textPrimary, // Texte principal avec couleur dynamique
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Affiche la date de publication sous forme relative ("il y a X heures").
|
||||||
|
Widget _buildPublicationDate(DateTime publicationDate) {
|
||||||
|
return Text(
|
||||||
|
'Il y a ${calculateTimeAgo(publicationDate)}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.textSecondary, // Texte secondaire avec couleur dynamique
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construit une carte spéciale pour ajouter une nouvelle story.
|
||||||
|
Widget _buildAddStoryCard(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
logger.i('Clic sur l\'ajout d\'une nouvelle story');
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => const CreateStoryPage()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: size.width / 4.5,
|
||||||
|
height: size.height / 5.5, // Hauteur ajustée pour éviter le dépassement
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.cardColor, // Utilisation des couleurs automatiques pour le fond des cartes
|
||||||
|
borderRadius: BorderRadius.circular(20), // Bords arrondis à 20 pixels
|
||||||
|
border: Border.all(color: AppColors.accentColor.withOpacity(0.3), width: 2),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.add_circle_outline,
|
||||||
|
color: AppColors.accentColor, // Utilisation des couleurs automatiques pour les icônes
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'Créer une story',
|
||||||
|
style: TextStyle(color: AppColors.textSecondary, fontSize: 13), // Texte secondaire avec couleur dynamique
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
75
lib/presentation/widgets/story_video_player.dart
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:video_player/video_player.dart'; // Pour la lecture des vidéos
|
||||||
|
|
||||||
|
class StoryVideoPlayer extends StatefulWidget {
|
||||||
|
final String mediaUrl;
|
||||||
|
|
||||||
|
const StoryVideoPlayer({super.key, required this.mediaUrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
StoryVideoPlayerState createState() => StoryVideoPlayerState(); // Classe publique
|
||||||
|
}
|
||||||
|
|
||||||
|
class StoryVideoPlayerState extends State<StoryVideoPlayer> {
|
||||||
|
VideoPlayerController? _videoPlayerController;
|
||||||
|
bool _loadingError = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initializeVideoPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initializeVideoPlayer() async {
|
||||||
|
_videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.mediaUrl));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _videoPlayerController!.initialize();
|
||||||
|
setState(() {
|
||||||
|
_loadingError = false;
|
||||||
|
_videoPlayerController!.play();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setState(() {
|
||||||
|
_loadingError = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_videoPlayerController?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (_loadingError) {
|
||||||
|
return _buildRetryUI();
|
||||||
|
} else if (_videoPlayerController != null && _videoPlayerController!.value.isInitialized) {
|
||||||
|
return VideoPlayer(_videoPlayerController!);
|
||||||
|
} else {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRetryUI() {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text('Problème de connexion ou de chargement', style: TextStyle(color: Colors.white)),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_initializeVideoPlayer();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Text('Réessayer'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
libs/arm64-v8a/libargon2.so
Normal file
BIN
libs/armeabi-v7a/libargon2.so
Normal file
BIN
libs/x86/libargon2.so
Normal file
BIN
libs/x86_64/libargon2.so
Normal file
@@ -6,9 +6,13 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||||
|
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
file_selector_linux
|
||||||
flutter_secure_storage_linux
|
flutter_secure_storage_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,16 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import file_selector_macos
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
import video_player_avfoundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
|
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
native_libs/argon2.dll
Normal file
BIN
obj/local/arm64-v8a/libargon2.so
Normal file
BIN
obj/local/arm64-v8a/objs/argon2/src/argon2.o
Normal file
7
obj/local/arm64-v8a/objs/argon2/src/argon2.o.d
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
./obj/local/arm64-v8a/objs/argon2/src/argon2.o: \
|
||||||
|
android/app/src/main/jni/src/argon2.c \
|
||||||
|
android/app/src/main/jni/argon2.h android/app/src/main/jni/encoding.h \
|
||||||
|
android/app/src/main/jni/core.h
|
||||||
|
android/app/src/main/jni/argon2.h:
|
||||||
|
android/app/src/main/jni/encoding.h:
|
||||||
|
android/app/src/main/jni/core.h:
|
||||||