refactoring and checkpoint

This commit is contained in:
DahoudG
2024-09-24 00:32:20 +00:00
parent dc73ba7dcc
commit 6b12cfeb41
159 changed files with 8119 additions and 1535 deletions

View File

@@ -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}"

View 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)

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}

View 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

View 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

View 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 */

View 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 */

View 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

View 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 */

View 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;
}

View 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;
}

View 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);
}
}
}
}

View 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 */

View 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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
View File

BIN
flutter_02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

BIN
flutter_03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

BIN
flutter_04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

BIN
lib/assets/videos/test.mp4 Normal file

Binary file not shown.

View 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.");
} }

View File

@@ -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(

View File

@@ -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;
}
}

View File

@@ -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
} }

View File

@@ -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";
}

View File

@@ -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),
),
),
); );
} }

View 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;
}
}

View 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';
}
}

View File

@@ -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);
} }
} }

View File

@@ -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.');
} }
} }
} }

View File

@@ -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");
} }
} }
} }

View File

@@ -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,
}; };
} }
} }

View File

@@ -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)
}; };
} }
} }

View File

@@ -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");
}
} }
} }

View File

@@ -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
}

View 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.");
}
}
}

View File

@@ -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.");
} }
} }

View File

@@ -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.");
} }
} }

View File

@@ -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.");
}
} }

View File

@@ -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());
} }
} }

View File

@@ -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: '/',
);
},
), ),
); );
} }

View File

@@ -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,
); );
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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
},
);
},
), ),
); );
} }

View File

@@ -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,
), ),
), ],
); );
} }
} }

View File

@@ -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.')),
);
} }
} }

View File

@@ -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 damis',
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,
),
);
}
} }

View File

@@ -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);
},
),
),
); );
} }
} }

View File

@@ -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();
}
} }

View File

@@ -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,
);
}
} }

View File

@@ -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'),
),
);
}
}

View File

@@ -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),
); );
} }
} }

View 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,
);
}
}

View File

@@ -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.'));
} }
} }
} }

View File

@@ -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 {}

View 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)),
],
);
}
}

View 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.');
}
},
),
],
),
);
}
}

View File

@@ -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,
), ),

View 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');
}
}

View 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'),
),
],
),
);
}),
);
}
}

View 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'),
),
],
),
);
}),
);
}
}

View 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,
),
),
],
),
),
);
},
),
);
}
}

View 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,
),
],
);
}
}

View 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,
),
),
],
),
),
);
},
),
);
}
}

View 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),
],
);
}
}

View 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'),
],
),
),
],
),
),
);
}
}

View 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
),
],
),
);
}
}

View 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

Binary file not shown.

Binary file not shown.

BIN
libs/x86/libargon2.so Normal file

Binary file not shown.

BIN
libs/x86_64/libargon2.so Normal file

Binary file not shown.

View 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);

View File

@@ -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
) )

View File

@@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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:

Some files were not shown because too many files have changed in this diff Show More