diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8d833d6..26b0936 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,17 @@ + + + + + + + + + + +#include +#include + +#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 diff --git a/android/app/src/main/jni/blake2-impl.h b/android/app/src/main/jni/blake2-impl.h new file mode 100644 index 0000000..86d0d5c --- /dev/null +++ b/android/app/src/main/jni/blake2-impl.h @@ -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 +#include + +#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 diff --git a/android/app/src/main/jni/blake2.h b/android/app/src/main/jni/blake2.h new file mode 100644 index 0000000..501c6a3 --- /dev/null +++ b/android/app/src/main/jni/blake2.h @@ -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 + +#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 diff --git a/android/app/src/main/jni/core.h b/android/app/src/main/jni/core.h new file mode 100644 index 0000000..59e2564 --- /dev/null +++ b/android/app/src/main/jni/core.h @@ -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 + */ +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 diff --git a/android/app/src/main/jni/encoding.h b/android/app/src/main/jni/encoding.h new file mode 100644 index 0000000..5b8b2dd --- /dev/null +++ b/android/app/src/main/jni/encoding.h @@ -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 diff --git a/android/app/src/main/jni/src/argon2.c b/android/app/src/main/jni/src/argon2.c new file mode 100644 index 0000000..34da3d6 --- /dev/null +++ b/android/app/src/main/jni/src/argon2.c @@ -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 +#include +#include + +#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; +} diff --git a/android/app/src/main/jni/src/blake2/blake2-impl.h b/android/app/src/main/jni/src/blake2/blake2-impl.h new file mode 100644 index 0000000..86d0d5c --- /dev/null +++ b/android/app/src/main/jni/src/blake2/blake2-impl.h @@ -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 +#include + +#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 diff --git a/android/app/src/main/jni/src/blake2/blake2.h b/android/app/src/main/jni/src/blake2/blake2.h new file mode 100644 index 0000000..501c6a3 --- /dev/null +++ b/android/app/src/main/jni/src/blake2/blake2.h @@ -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 + +#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 diff --git a/android/app/src/main/jni/src/blake2/blake2b.c b/android/app/src/main/jni/src/blake2/blake2b.c new file mode 100644 index 0000000..3b519dd --- /dev/null +++ b/android/app/src/main/jni/src/blake2/blake2b.c @@ -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 +#include +#include + +#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 */ diff --git a/android/app/src/main/jni/src/blake2/blamka-round-opt.h b/android/app/src/main/jni/src/blake2/blamka-round-opt.h new file mode 100644 index 0000000..3127f2a --- /dev/null +++ b/android/app/src/main/jni/src/blake2/blamka-round-opt.h @@ -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 +#if defined(__SSSE3__) +#include /* for _mm_shuffle_epi8 and _mm_alignr_epi8 */ +#endif + +#if defined(__XOP__) && (defined(__GNUC__) || defined(__clang__)) +#include +#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 + +#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 + +#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 */ diff --git a/android/app/src/main/jni/src/blake2/blamka-round-ref.h b/android/app/src/main/jni/src/blake2/blamka-round-ref.h new file mode 100644 index 0000000..16cfc1c --- /dev/null +++ b/android/app/src/main/jni/src/blake2/blamka-round-ref.h @@ -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 diff --git a/android/app/src/main/jni/src/blake2b.c b/android/app/src/main/jni/src/blake2b.c new file mode 100644 index 0000000..3b519dd --- /dev/null +++ b/android/app/src/main/jni/src/blake2b.c @@ -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 +#include +#include + +#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 */ diff --git a/android/app/src/main/jni/src/core.c b/android/app/src/main/jni/src/core.c new file mode 100644 index 0000000..e697882 --- /dev/null +++ b/android/app/src/main/jni/src/core.c @@ -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 +#include /* 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 +#include +#include + +#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.. 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; +} diff --git a/android/app/src/main/jni/src/encoding.c b/android/app/src/main/jni/src/encoding.c new file mode 100644 index 0000000..771a440 --- /dev/null +++ b/android/app/src/main/jni/src/encoding.c @@ -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 +#include +#include +#include +#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 , + * 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[$v=]$m=,t=,p=$$ + * + * where is either 'd', 'id', or 'i', is a decimal integer (positive, + * fits in an 'unsigned long'), and 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; +} + diff --git a/android/app/src/main/jni/src/ref.c b/android/app/src/main/jni/src/ref.c new file mode 100644 index 0000000..10e45eb --- /dev/null +++ b/android/app/src/main/jni/src/ref.c @@ -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 +#include +#include + +#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); + } + } + } +} diff --git a/android/app/src/main/jni/src/thread.c b/android/app/src/main/jni/src/thread.c new file mode 100644 index 0000000..3ae2fb2 --- /dev/null +++ b/android/app/src/main/jni/src/thread.c @@ -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 +#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 */ diff --git a/android/app/src/main/jni/thread.h b/android/app/src/main/jni/thread.h new file mode 100644 index 0000000..d4ca10c --- /dev/null +++ b/android/app/src/main/jni/thread.h @@ -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 +typedef unsigned(__stdcall *argon2_thread_func_t)(void *); +typedef uintptr_t argon2_thread_handle_t; +#else +#include +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 diff --git a/android/app/src/main/jniLibs/arm64-v8a/libargon2.so b/android/app/src/main/jniLibs/arm64-v8a/libargon2.so new file mode 100644 index 0000000..07a4e36 Binary files /dev/null and b/android/app/src/main/jniLibs/arm64-v8a/libargon2.so differ diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libargon2.so b/android/app/src/main/jniLibs/armeabi-v7a/libargon2.so new file mode 100644 index 0000000..773dff8 Binary files /dev/null and b/android/app/src/main/jniLibs/armeabi-v7a/libargon2.so differ diff --git a/android/app/src/main/jniLibs/x86/libargon2.so b/android/app/src/main/jniLibs/x86/libargon2.so new file mode 100644 index 0000000..9921cb7 Binary files /dev/null and b/android/app/src/main/jniLibs/x86/libargon2.so differ diff --git a/android/app/src/main/jniLibs/x86_64/libargon2.so b/android/app/src/main/jniLibs/x86_64/libargon2.so new file mode 100644 index 0000000..5413a6b Binary files /dev/null and b/android/app/src/main/jniLibs/x86_64/libargon2.so differ diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index 7e48e13..0a0938a 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,9 +1,3 @@ - - - - + diff --git a/flutter_01.png b/flutter_01.png new file mode 100644 index 0000000..e69de29 diff --git a/flutter_02.png b/flutter_02.png new file mode 100644 index 0000000..f5cd612 Binary files /dev/null and b/flutter_02.png differ diff --git a/flutter_03.png b/flutter_03.png new file mode 100644 index 0000000..8ba7e4d Binary files /dev/null and b/flutter_03.png differ diff --git a/flutter_04.png b/flutter_04.png new file mode 100644 index 0000000..c45b299 Binary files /dev/null and b/flutter_04.png differ diff --git a/lib/assets/fonts/montserrat_bold.ttf b/lib/assets/fonts/montserrat_bold.ttf new file mode 100644 index 0000000..0927b81 Binary files /dev/null and b/lib/assets/fonts/montserrat_bold.ttf differ diff --git a/lib/assets/fonts/montserrat_regular.ttf b/lib/assets/fonts/montserrat_regular.ttf new file mode 100644 index 0000000..f4a266d Binary files /dev/null and b/lib/assets/fonts/montserrat_regular.ttf differ diff --git a/lib/assets/images/activity_placeholder.png b/lib/assets/images/activity_placeholder.png new file mode 100644 index 0000000..26eb27f Binary files /dev/null and b/lib/assets/images/activity_placeholder.png differ diff --git a/lib/assets/images/event_placeholder.png b/lib/assets/images/event_placeholder.png new file mode 100644 index 0000000..c860cd5 Binary files /dev/null and b/lib/assets/images/event_placeholder.png differ diff --git a/lib/assets/images/friend_placeholder.png b/lib/assets/images/friend_placeholder.png new file mode 100644 index 0000000..3392c86 Binary files /dev/null and b/lib/assets/images/friend_placeholder.png differ diff --git a/lib/assets/images/group_placeholder.png b/lib/assets/images/group_placeholder.png new file mode 100644 index 0000000..2518ba3 Binary files /dev/null and b/lib/assets/images/group_placeholder.png differ diff --git a/lib/assets/images/logo.png b/lib/assets/images/logo.png index 847469e..2d307a6 100644 Binary files a/lib/assets/images/logo.png and b/lib/assets/images/logo.png differ diff --git a/lib/assets/images/logoaw.png b/lib/assets/images/logoaw.png new file mode 100644 index 0000000..847469e Binary files /dev/null and b/lib/assets/images/logoaw.png differ diff --git a/lib/assets/images/logolionsdev.png b/lib/assets/images/logolionsdev.png new file mode 100644 index 0000000..7a3b646 Binary files /dev/null and b/lib/assets/images/logolionsdev.png differ diff --git a/lib/assets/images/story_placeholder.png b/lib/assets/images/story_placeholder.png new file mode 100644 index 0000000..032827b Binary files /dev/null and b/lib/assets/images/story_placeholder.png differ diff --git a/lib/assets/images/user_placeholder.png b/lib/assets/images/user_placeholder.png new file mode 100644 index 0000000..995b4bc Binary files /dev/null and b/lib/assets/images/user_placeholder.png differ diff --git a/lib/assets/videos/test.mp4 b/lib/assets/videos/test.mp4 new file mode 100644 index 0000000..24b738c Binary files /dev/null and b/lib/assets/videos/test.mp4 differ diff --git a/lib/config/injection/injection.dart b/lib/config/injection/injection.dart index 5197f95..5125c33 100644 --- a/lib/config/injection/injection.dart +++ b/lib/config/injection/injection.dart @@ -5,18 +5,31 @@ import '../../data/datasources/user_remote_data_source.dart'; import '../../data/repositories/user_repository_impl.dart'; import '../../domain/usecases/get_user.dart'; +/// Instance globale pour gérer l'injection des dépendances via GetIt 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() { + // Log de démarrage de l'injection des dépendances + print("Démarrage de l'initialisation des dépendances."); + // Register Http Client sl.registerLazySingleton(() => http.Client()); + print("Client HTTP enregistré."); // Register Data Sources sl.registerLazySingleton(() => UserRemoteDataSource(sl())); + print("DataSource pour UserRemoteDataSource enregistré."); // Register Repositories sl.registerLazySingleton(() => UserRepositoryImpl(remoteDataSource: sl())); + print("Repository pour UserRepositoryImpl enregistré."); // Register Use Cases sl.registerLazySingleton(() => GetUser(sl())); + print("UseCase pour GetUser enregistré."); + + // Log de fin d'initialisation des dépendances + print("Initialisation des dépendances terminée."); } diff --git a/lib/config/router.dart b/lib/config/router.dart index bf91590..d862690 100644 --- a/lib/config/router.dart +++ b/lib/config/router.dart @@ -1,30 +1,39 @@ import 'package:flutter/material.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/story/story_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/home/home_screen.dart'; import 'package:afterwork/data/datasources/event_remote_data_source.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 { final EventRemoteDataSource eventRemoteDataSource; final String userId; final String userName; final String userLastName; + /// Initialisation des informations utilisateur et source de données AppRouter({ required this.eventRemoteDataSource, required this.userId, required this.userName, required this.userLastName, - }); + }) { + print("AppRouter initialisé avec les infos utilisateur : $userId, $userName, $userLastName"); + } + /// Génération des routes pour l'application Route generateRoute(RouteSettings settings) { + print("Navigation vers la route : ${settings.name}"); + switch (settings.name) { case '/': return MaterialPageRoute(builder: (_) => const LoginScreen()); + case '/home': return MaterialPageRoute( builder: (_) => HomeScreen( @@ -32,25 +41,31 @@ class AppRouter { userId: userId, userName: userName, userLastName: userLastName, + userProfileImage: 'lib/assets/images/profile_picture.png', ), ); + case '/event': return MaterialPageRoute( builder: (_) => EventScreen( - eventRemoteDataSource: eventRemoteDataSource, userId: userId, userName: userName, userLastName: userLastName, ), ); + case '/story': return MaterialPageRoute(builder: (_) => const StoryScreen()); + case '/profile': return MaterialPageRoute(builder: (_) => const ProfileScreen()); + case '/settings': return MaterialPageRoute(builder: (_) => const SettingsScreen()); + case '/reservations': return MaterialPageRoute(builder: (_) => const ReservationsScreen()); + default: return MaterialPageRoute( builder: (_) => const Scaffold( diff --git a/lib/core/constants/colors.dart b/lib/core/constants/colors.dart index e69de29..2b35052 100644 --- a/lib/core/constants/colors.dart +++ b/lib/core/constants/colors.dart @@ -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; + } +} diff --git a/lib/core/constants/urls.dart b/lib/core/constants/urls.dart index 7e3175c..bf2d1bd 100644 --- a/lib/core/constants/urls.dart +++ b/lib/core/constants/urls.dart @@ -1,6 +1,31 @@ class Urls { static const String baseUrl = 'http://192.168.1.145:8085'; - // static const String login = baseUrl + 'auth/login'; - static const String eventsUrl = '$baseUrl/events'; -// Ajoute d'autres URLs ici + + // Authentication and Users Endpoints + 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 } diff --git a/lib/core/errors/exceptions.dart b/lib/core/errors/exceptions.dart index cc7ff5f..cea5c5b 100644 --- a/lib/core/errors/exceptions.dart +++ b/lib/core/errors/exceptions.dart @@ -27,3 +27,26 @@ class ServerExceptionWithMessage implements Exception { 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"; +} \ No newline at end of file diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index 6a9666f..cabb77d 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -1,20 +1,116 @@ 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 { + /// Thème clair static final ThemeData lightTheme = ThemeData( - primaryColor: Colors.blue, - colorScheme: const ColorScheme.light( - secondary: Colors.orange, - ), 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( - primaryColor: Colors.black, - colorScheme: const ColorScheme.dark( - secondary: Colors.red, - ), 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), + ), + ), ); } + diff --git a/lib/core/theme/theme_provider.dart b/lib/core/theme/theme_provider.dart new file mode 100644 index 0000000..14003dc --- /dev/null +++ b/lib/core/theme/theme_provider.dart @@ -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; + } +} diff --git a/lib/core/utils/calculate_time_ago.dart b/lib/core/utils/calculate_time_ago.dart new file mode 100644 index 0000000..395a9fc --- /dev/null +++ b/lib/core/utils/calculate_time_ago.dart @@ -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'; + } +} diff --git a/lib/core/utils/date_formatter.dart b/lib/core/utils/date_formatter.dart index bf0f3ef..a4f4d6f 100644 --- a/lib/core/utils/date_formatter.dart +++ b/lib/core/utils/date_formatter.dart @@ -2,6 +2,7 @@ import 'package:intl/intl.dart'; class DateFormatter { 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); } } diff --git a/lib/data/datasources/event_remote_data_source.dart b/lib/data/datasources/event_remote_data_source.dart index 186a473..e0b1842 100644 --- a/lib/data/datasources/event_remote_data_source.dart +++ b/lib/data/datasources/event_remote_data_source.dart @@ -33,7 +33,7 @@ class EventRemoteDataSource { print('Création d\'un nouvel événement avec les données: ${event.toJson()}'); final response = await client.post( - Uri.parse(Urls.eventsUrl), + Uri.parse(Urls.createEvent), headers: {'Content-Type': 'application/json'}, body: jsonEncode(event.toJson()), ); @@ -53,7 +53,7 @@ class EventRemoteDataSource { Future getEventById(String id) async { 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}'); @@ -71,7 +71,7 @@ class EventRemoteDataSource { print('Mise à jour de l\'événement avec l\'ID: $id, données: ${event.toJson()}'); final response = await client.put( - Uri.parse('${Urls.eventsUrl}/$id'), + Uri.parse('${Urls.updateEvent}/$id'), headers: {'Content-Type': 'application/json'}, body: jsonEncode(event.toJson()), ); @@ -91,7 +91,7 @@ class EventRemoteDataSource { Future deleteEvent(String id) async { 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}'); @@ -108,7 +108,7 @@ class EventRemoteDataSource { print('Participation à l\'événement avec l\'ID: $eventId, utilisateur: $userId'); final response = await client.post( - Uri.parse('${Urls.eventsUrl}/$eventId/participate'), + Uri.parse('${Urls.addParticipant}/$eventId/participate'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({'userId': userId}), ); @@ -129,7 +129,7 @@ class EventRemoteDataSource { print('Réaction à l\'événement avec l\'ID: $eventId, utilisateur: $userId'); final response = await client.post( - Uri.parse('${Urls.eventsUrl}/$eventId/react'), + Uri.parse('${Urls.baseUrl}/$eventId/react'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({'userId': userId}), ); @@ -149,7 +149,7 @@ class EventRemoteDataSource { print('Fermeture de l\'événement avec l\'ID: $eventId'); final response = await client.post( - Uri.parse('${Urls.eventsUrl}/$eventId/close'), + Uri.parse('${Urls.closeEvent}/$eventId/close'), headers: {'Content-Type': 'application/json'}, ); @@ -174,7 +174,7 @@ class EventRemoteDataSource { print('Réouverture de l\'événement avec l\'ID: $eventId'); final response = await client.post( - Uri.parse('${Urls.eventsUrl}/$eventId/reopen'), + Uri.parse('${Urls.baseUrl}/$eventId/reopen'), 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.'); } } + } diff --git a/lib/data/datasources/user_remote_data_source.dart b/lib/data/datasources/user_remote_data_source.dart index 7cdd341..92bcc65 100644 --- a/lib/data/datasources/user_remote_data_source.dart +++ b/lib/data/datasources/user_remote_data_source.dart @@ -1,90 +1,141 @@ import 'dart:convert'; - import 'package:afterwork/core/constants/urls.dart'; import 'package:afterwork/data/models/user_model.dart'; import 'package:http/http.dart' as http; - 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 { final http.Client client; + /// Constructeur avec injection du client HTTP UserRemoteDataSource(this.client); - // Authentifier l'utilisateur - Future authenticateUser(String email, String password, String userId) async { - if (email.isEmpty || password.isEmpty) { - throw Exception('Email ou mot de passe vide'); - } + /// Authentifie un utilisateur avec l'email et le mot de passe en clair. + /// Si l'authentification réussit, retourne un objet `UserModel`. + Future authenticateUser(String email, String password) async { + print("Tentative d'authentification pour l'email : $email"); - final response = await client.post( - Uri.parse('${Urls.baseUrl}/users/authenticate'), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({ - 'email': email, - 'motDePasse': password, - }), - ); + try { + // Requête POST avec l'email et le mot de passe en clair + final response = await client.post( + Uri.parse('${Urls.baseUrl}/users/authenticate'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'email': email, + 'motDePasse': password, // Le mot de passe est envoyé en clair pour le moment + }), + ); - if (response.statusCode == 200) { - final jsonResponse = json.decode(response.body); - return UserModel.fromJson(jsonResponse); - } else if (response.statusCode == 401) { - throw AuthenticationException('Email ou mot de passe incorrect'); - } else { - throw ServerException(); + print("Réponse du serveur pour l'authentification : ${response.statusCode} - ${response.body}"); + + if (response.statusCode == 200) { + // Si l'authentification réussit, retourne l'utilisateur + return UserModel.fromJson(jsonDecode(response.body)); + } else if (response.statusCode == 401) { + // 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 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) { - return UserModel.fromJson(json.decode(response.body)); - } else { - throw ServerException(); + try { + final response = await client.get(Uri.parse('${Urls.baseUrl}/users/$id')); + print("Réponse du serveur pour getUser : ${response.statusCode} - ${response.body}"); + + 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 createUser(UserModel user) async { - final response = await client.post( - Uri.parse('${Urls.baseUrl}/users'), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(user.toJson()), - ); + print("Création d'un nouvel utilisateur : ${user.toJson()}"); - if (response.statusCode == 201) { - return UserModel.fromJson(json.decode(response.body)); - } else { - throw ServerException(); + try { + final response = await client.post( + Uri.parse('${Urls.baseUrl}/users'), + 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 updateUser(UserModel user) async { - final response = await client.put( - Uri.parse('${Urls.baseUrl}/users/${user.userId}'), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(user.toJson()), - ); + print("Mise à jour de l'utilisateur : ${user.toJson()}"); - if (response.statusCode == 200) { - return UserModel.fromJson(json.decode(response.body)); - } else { - throw ServerException(); + try { + final response = await client.put( + Uri.parse('${Urls.baseUrl}/users/${user.userId}'), + 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 deleteUser(String id) async { - final response = await client.delete( - Uri.parse('${Urls.baseUrl}/users/$id'), - ); + print("Tentative de suppression de l'utilisateur avec l'ID : $id"); - if (response.statusCode != 204) { - throw ServerException(); + try { + 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"); } } } diff --git a/lib/data/models/event_model.dart b/lib/data/models/event_model.dart index 7e6ca5a..47b00c0 100644 --- a/lib/data/models/event_model.dart +++ b/lib/data/models/event_model.dart @@ -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 { - final String id; // Identifiant unique de l'événement - final String title; // Titre de l'événement - final String description; // Description de l'événement - final String date; // Date de l'événement - final String location; // Localisation de l'événement - final String category; // Catégorie de l'événement - final String link; // Lien associé à l'événement - final String? imageUrl; // URL de l'image de l'événement (optionnel) - final UserModel creator; // Créateur de l'événement - final List participants; // Liste des participants à l'événement - final String status; // Statut de l'événement (e.g., "OPEN", "CLOSED") + final String id; + final String title; + final String description; + final String startDate; // Utiliser startDate au lieu de date, si c'est ce que l'API retourne + final String location; + final String category; + final String link; + final String? imageUrl; // Nullable + final String creatorEmail; // Remplacer UserModel si le créateur est un email + final List participants; // Si participants est une liste simple + 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({ required this.id, required this.title, required this.description, - required this.date, + required this.startDate, required this.location, required this.category, required this.link, this.imageUrl, - required this.creator, + required this.creatorEmail, required this.participants, required this.status, + required this.reactionsCount, + required this.commentsCount, + required this.sharesCount, }); - /// Convertit un objet JSON en `EventModel`. factory EventModel.fromJson(Map json) { - // Log de la conversion depuis JSON - print('Conversion de l\'objet JSON en EventModel: ${json['id']}'); - return EventModel( id: json['id'], title: json['title'], description: json['description'], - date: json['date'], + startDate: json['startDate'], // Vérifier si c'est bien startDate location: json['location'], category: json['category'], - link: json['link'] ?? '', // Assure qu'il ne soit pas null - imageUrl: json['imageUrl'] ?? '', // Assure qu'il ne soit pas null - status: json['status'], - creator: UserModel.fromJson(json['creator']), - participants: json['participants'] != null - ? (json['participants'] as List) - .map((user) => UserModel.fromJson(user)) - .toList() - : [], // Si participants est null, retourne une liste vide + link: json['link'] ?? '', + imageUrl: json['imageUrl'], // Peut être null + creatorEmail: json['creatorEmail'], // Email du créateur + participants: json['participants'] ?? [], // Gérer les participants + status: json['status'] ?? 'open', // Par défaut à "open" si non fourni + reactionsCount: json['reactionsCount'] ?? 0, + commentsCount: json['commentsCount'] ?? 0, + sharesCount: json['sharesCount'] ?? 0, ); } - /// Convertit un `EventModel` en objet JSON. Map toJson() { - // Log de la conversion en JSON - print('Conversion de l\'EventModel en objet JSON: $id'); - return { 'id': id, 'title': title, 'description': description, - 'date': date, + 'startDate': startDate, 'location': location, 'category': category, 'link': link, 'imageUrl': imageUrl, - 'creator': creator.toJson(), - 'participants': participants.map((user) => user.toJson()).toList(), + 'creatorEmail': creatorEmail, + 'participants': participants, 'status': status, + 'reactionsCount': reactionsCount, + 'commentsCount': commentsCount, + 'sharesCount': sharesCount, }; } } diff --git a/lib/data/models/user_model.dart b/lib/data/models/user_model.dart index 4c0276e..7fae62f 100644 --- a/lib/data/models/user_model.dart +++ b/lib/data/models/user_model.dart @@ -1,8 +1,10 @@ -import 'package:afterwork/domain/entities/user.dart'; +import '../../domain/entities/user.dart'; -class UserModel extends User { - const UserModel({ - required String userId, // Utilisez `id` pour correspondre à l'entité User +/// Modèle représentant l'utilisateur dans l'application AfterWork. +/// Ce modèle est utilisé pour la conversion JSON et l'interaction avec l'API. +class UserModel extends User { + UserModel({ + required String userId, required String nom, required String prenoms, required String email, @@ -15,23 +17,25 @@ class UserModel extends User { motDePasse: motDePasse, ); + /// Factory pour créer un `UserModel` à partir d'un JSON reçu depuis l'API. factory UserModel.fromJson(Map json) { return UserModel( userId: json['id'] ?? '', - nom: json['nom'] ?? '', - prenoms: json['prenoms'] ?? '', - email: json['email'] ?? '', + nom: json['nom'] ?? 'Inconnu', + prenoms: json['prenoms'] ?? 'Inconnu', + email: json['email'] ?? 'inconnu@example.com', motDePasse: json['motDePasse'] ?? '', ); } + /// Convertit le `UserModel` en JSON pour l'envoi vers l'API. Map toJson() { return { - 'id': userId, // Utilisez `id` pour correspondre à l'entité User + 'id': userId, 'nom': nom, 'prenoms': prenoms, 'email': email, - 'motDePasse': motDePasse, + 'motDePasse': motDePasse, // Mot de passe en clair (comme demandé temporairement) }; } } diff --git a/lib/data/repositories/user_repository_impl.dart b/lib/data/repositories/user_repository_impl.dart index 7888fa2..066c9cb 100644 --- a/lib/data/repositories/user_repository_impl.dart +++ b/lib/data/repositories/user_repository_impl.dart @@ -1,21 +1,49 @@ -import 'package:afterwork/domain/entities/user.dart'; -import 'package:afterwork/domain/repositories/user_repository.dart'; +import 'dart:convert'; import 'package:afterwork/data/datasources/user_remote_data_source.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 { final UserRemoteDataSource remoteDataSource; + /// Constructeur avec injection de la source de données distante. UserRepositoryImpl({required this.remoteDataSource}); + /// Récupère un utilisateur par son ID depuis la source de données distante. @override Future getUser(String id) async { 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 authenticateUser(String email, String password, String userId) async { - UserModel userModel = await remoteDataSource.authenticateUser(email, password, userId); - return userModel; // Retourne un UserModel qui est un sous-type de User + /// Authentifie un utilisateur par email et mot de passe (en clair, temporairement). + Future authenticateUser(String email, String password) async { + 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"); + } } } diff --git a/lib/data/services/hash_password.dart b/lib/data/services/hash_password.dart deleted file mode 100644 index 1de1c03..0000000 --- a/lib/data/services/hash_password.dart +++ /dev/null @@ -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 -} diff --git a/lib/data/services/hash_password_service.dart b/lib/data/services/hash_password_service.dart new file mode 100644 index 0000000..b4bc86d --- /dev/null +++ b/lib/data/services/hash_password_service.dart @@ -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 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 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."); + } + } + +} diff --git a/lib/data/services/preferences_helper.dart b/lib/data/services/preferences_helper.dart index 194083c..edb80d5 100644 --- a/lib/data/services/preferences_helper.dart +++ b/lib/data/services/preferences_helper.dart @@ -1,50 +1,82 @@ 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 { + // Initialisation de SharedPreferences en tant que Future final Future _prefs = SharedPreferences.getInstance(); + /// Sauvegarde une chaîne de caractères (String) dans les préférences. Future setString(String key, String value) async { + print("Sauvegarde dans les préférences : clé = $key, valeur = $value"); final prefs = await _prefs; 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 getString(String key) async { + print("Récupération depuis les préférences pour la clé : $key"); 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 remove(String key) async { + print("Suppression dans les préférences pour la clé : $key"); final prefs = await _prefs; await prefs.remove(key); + print("Suppression réussie pour la clé : $key"); } + /// Sauvegarde l'identifiant utilisateur dans les préférences. Future saveUserId(String userId) async { + print("Sauvegarde de l'userId dans les préférences : $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 getUserId() async { + print("Récupération de l'userId depuis les préférences."); return await getString('user_id'); } + /// Sauvegarde le nom d'utilisateur dans les préférences. Future saveUserName(String userName) async { + print("Sauvegarde du userName dans les préférences : $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 getUserName() async { + print("Récupération du userName depuis les préférences."); return await getString('user_name'); } + /// Sauvegarde le prénom de l'utilisateur dans les préférences. Future saveUserLastName(String userLastName) async { + print("Sauvegarde du userLastName dans les préférences : $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 getUserLastName() async { + print("Récupération du userLastName depuis les préférences."); return await getString('user_last_name'); } + /// Supprime toutes les informations utilisateur dans les préférences. Future clearUserInfo() async { + print("Suppression des informations utilisateur (userId, userName, userLastName) des préférences."); await remove('user_id'); await remove('user_name'); await remove('user_last_name'); + print("Suppression réussie des informations utilisateur."); } } diff --git a/lib/data/services/secure_storage.dart b/lib/data/services/secure_storage.dart index 7c61670..6ccea61 100644 --- a/lib/data/services/secure_storage.dart +++ b/lib/data/services/secure_storage.dart @@ -1,47 +1,78 @@ 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 { + // Instance de FlutterSecureStorage pour gérer le stockage sécurisé final FlutterSecureStorage _storage = const FlutterSecureStorage(); + /// Écrit une valeur dans le stockage sécurisé avec la clé spécifiée. Future write(String key, String value) async { + print("Écriture dans le stockage sécurisé : clé = $key, valeur = $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 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 delete(String key) async { + print("Suppression dans le stockage sécurisé pour la clé : $key"); await _storage.delete(key: key); + print("Suppression réussie pour la clé : $key"); } + /// Sauvegarde l'identifiant utilisateur dans le stockage sécurisé. Future saveUserId(String userId) async { + print("Sauvegarde de l'userId dans le stockage sécurisé : $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 getUserId() async { + print("Récupération de l'userId depuis le stockage sécurisé."); return await read('user_id'); } + /// Sauvegarde le nom d'utilisateur dans le stockage sécurisé. Future saveUserName(String userName) async { + print("Sauvegarde du userName dans le stockage sécurisé : $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 getUserName() async { + print("Récupération du userName depuis le stockage sécurisé."); return await read('user_name'); } + /// Sauvegarde le prénom de l'utilisateur dans le stockage sécurisé. Future saveUserLastName(String userLastName) async { + print("Sauvegarde du userLastName dans le stockage sécurisé : $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 getUserLastName() async { + print("Récupération du userLastName depuis le stockage sécurisé."); return await read('user_last_name'); } + /// Supprime toutes les informations utilisateur du stockage sécurisé. Future deleteUserInfo() async { + print("Suppression des informations utilisateur (userId, userName, userLastName)."); await delete('user_id'); await delete('user_name'); await delete('user_last_name'); + print("Suppression réussie des informations utilisateur."); } } diff --git a/lib/domain/repositories/user_repository.dart b/lib/domain/repositories/user_repository.dart index 4491667..4e1aa07 100644 --- a/lib/domain/repositories/user_repository.dart +++ b/lib/domain/repositories/user_repository.dart @@ -1,5 +1,13 @@ 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 { - Future 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 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."); + } } diff --git a/lib/domain/usecases/get_user.dart b/lib/domain/usecases/get_user.dart index c9f8077..87e7f5c 100644 --- a/lib/domain/usecases/get_user.dart +++ b/lib/domain/usecases/get_user.dart @@ -3,16 +3,28 @@ import 'package:afterwork/domain/entities/user.dart'; import 'package:afterwork/domain/repositories/user_repository.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 { - 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> call(String id) async { + print("Appel à GetUser avec l'ID : $id"); + try { + // Appel au dépôt pour récupérer l'utilisateur final user = await repository.getUser(id); + print("Utilisateur récupéré avec succès : ${user.userId}"); return Right(user); } catch (e) { + print("Erreur lors de la récupération de l'utilisateur : $e"); return Left(ServerFailure()); } } diff --git a/lib/main.dart b/lib/main.dart index 3bf4ed3..605dc94 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,32 +1,35 @@ +import 'package:flutter/material.dart'; import 'package:afterwork/config/router.dart'; import 'package:afterwork/data/datasources/event_remote_data_source.dart'; import 'package:afterwork/data/providers/user_provider.dart'; import 'package:afterwork/data/services/preferences_helper.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:intl/date_symbol_data_local.dart'; import 'package:provider/provider.dart'; +import 'core/theme/theme_provider.dart'; + void main() async { 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); + // Initialisation des services nécessaires final eventRemoteDataSource = EventRemoteDataSource(http.Client()); - - // Remplacez ici par l'utilisation du stockage sécurisé ou des préférences final SecureStorage secureStorage = SecureStorage(); final PreferencesHelper preferencesHelper = PreferencesHelper(); + // Récupération des informations stockées String? userId = await secureStorage.getUserId(); String? userName = await preferencesHelper.getUserName(); String? userLastName = await preferencesHelper.getUserLastName(); - // Si les valeurs sont nulles, vous pouvez définir des valeurs par défaut ou gérer autrement - userId ??= - 'default_user_id'; // Remplacer par une valeur par défaut si nécessaire + // Gestion des valeurs par défaut si nécessaires + userId ??= 'default_user_id'; userName ??= 'Default'; userLastName ??= 'User'; @@ -57,23 +60,29 @@ class MyApp extends StatelessWidget { return MultiProvider( providers: [ ChangeNotifierProvider( - create: (_) => - UserProvider()..setUser(userId, userName, userLastName), + create: (_) => 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( - title: 'AfterWork', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - onGenerateRoute: AppRouter( - eventRemoteDataSource: eventRemoteDataSource, - userId: userId, - userName: userName, - userLastName: userLastName, - ).generateRoute, - initialRoute: '/', + child: Consumer( + builder: (context, themeProvider, _) { + return MaterialApp( + title: 'AfterWork', + theme: themeProvider.currentTheme, + onGenerateRoute: AppRouter( + eventRemoteDataSource: eventRemoteDataSource, + userId: userId, + userName: userName, + userLastName: userLastName, + ).generateRoute, + initialRoute: '/', + ); + }, ), ); } diff --git a/lib/presentation/reservations/reservations_screen.dart b/lib/presentation/reservations/reservations_screen.dart index 464815e..b4680cb 100644 --- a/lib/presentation/reservations/reservations_screen.dart +++ b/lib/presentation/reservations/reservations_screen.dart @@ -1,38 +1,39 @@ 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 { - const ReservationsScreen({super.key}); + const ReservationsScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { + print("Affichage de l'écran des réservations."); + return Scaffold( appBar: AppBar( title: const Text('Réservations'), - backgroundColor: Colors.black, - elevation: 0, + backgroundColor: Colors.blueAccent, ), - body: ListView( - children: [ - ListTile( - leading: const Icon(Icons.event, color: Colors.blueAccent), - title: const Text('Réservation 1', style: TextStyle(color: Colors.white)), - subtitle: const Text('Détails de la réservation 1', style: TextStyle(color: Colors.white70)), - onTap: () { - // Logique pour afficher les détails de la réservation 1 - }, - ), - ListTile( - leading: const Icon(Icons.event, color: Colors.blueAccent), - title: const Text('Réservation 2', style: TextStyle(color: Colors.white)), - subtitle: const Text('Détails de la réservation 2', style: TextStyle(color: Colors.white70)), - onTap: () { - // Logique pour afficher les détails de la réservation 2 - }, - ), - // Ajoutez d'autres ListTile pour les autres réservations - ], + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Aucune réservation trouvée', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () { + print("L'utilisateur a appuyé sur le bouton 'Ajouter une réservation'."); + // Logique pour ajouter une réservation + }, + child: const Text('Ajouter une réservation'), + ), + ], + ), ), - backgroundColor: Colors.black, ); } } diff --git a/lib/presentation/screens/dialogs/add_event_dialog.dart b/lib/presentation/screens/dialogs/add_event_dialog.dart index d714bf9..9800012 100644 --- a/lib/presentation/screens/dialogs/add_event_dialog.dart +++ b/lib/presentation/screens/dialogs/add_event_dialog.dart @@ -1,443 +1,110 @@ 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 { final String userId; final String userName; final String userLastName; const AddEventDialog({ - super.key, + Key? key, required this.userId, required this.userName, required this.userLastName, - }); + }) : super(key: key); @override _AddEventDialogState createState() => _AddEventDialogState(); } class _AddEventDialogState extends State { - final _formKey = GlobalKey(); - - // Variables pour stocker les données 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> _categories = {}; - List _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] ?? []; - }); - } + final _formKey = GlobalKey(); // Clé pour valider le formulaire + String _eventName = ''; // Nom de l'événement + DateTime _selectedDate = DateTime.now(); // Date de l'événement @override Widget build(BuildContext context) { - return Dialog( - 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(), - ], - ), - ), - ), - ), - ); - } + print("Affichage du dialogue d'ajout d'événement."); - Widget _buildTitleField() { - return TextFormField( - decoration: InputDecoration( - labelText: 'Titre', - 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.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, + return AlertDialog( + title: const Text('Ajouter un événement'), + content: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Text( - _selectedDate == null - ? 'Sélectionnez une date' - : '${_selectedDate!.day}/${_selectedDate!.month}/${_selectedDate!.year}', - style: const TextStyle(color: Colors.white70), + // Champ pour entrer le nom de l'événement + TextFormField( + decoration: const InputDecoration(labelText: 'Nom de l\'événement'), + onSaved: (value) { + _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), ], ), ), - ); - } - - Widget _buildLocationField(BuildContext context) { - return GestureDetector( - onTap: () async { - 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( - 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( - 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'); - }); + actions: [ + // Bouton pour annuler l'ajout de l'événement + TextButton( + onPressed: () { + print("L'utilisateur a annulé l'ajout de l'événement."); + Navigator.of(context).pop(); }, + child: const Text('Annuler'), ), - const SizedBox(height: 10), - DropdownButtonFormField( - decoration: InputDecoration( - labelText: '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: _category.isNotEmpty ? _category : null, - items: _currentCategories.map((String category) { - return DropdownMenuItem( - 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'; + // Bouton pour soumettre le formulaire et ajouter l'événement + TextButton( + onPressed: () { + if (_formKey.currentState?.validate() == true) { + _formKey.currentState?.save(); + print("L'utilisateur a ajouté un événement : Nom = $_eventName, Date = $_selectedDate"); + Navigator.of(context).pop({ + 'eventName': _eventName, + 'eventDate': _selectedDate, + }); } - return null; }, + child: const Text('Ajouter'), ), ], ); } - Widget _buildImagePicker() { - return GestureDetector( - onTap: () { - // Logique pour sélectionner une image - print('Image Picker activé'); - }, - 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( - _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 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)), + /// Fonction pour afficher le sélecteur de date + Future _selectDate(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: _selectedDate, + firstDate: DateTime(2000), + lastDate: DateTime(2101), ); + if (picked != null) { + print("Date choisie dans le sélecteur : $picked"); + } + return picked; } } diff --git a/lib/presentation/screens/establishments/establishments_screen.dart b/lib/presentation/screens/establishments/establishments_screen.dart index 07296f5..e9577bd 100644 --- a/lib/presentation/screens/establishments/establishments_screen.dart +++ b/lib/presentation/screens/establishments/establishments_screen.dart @@ -1,18 +1,35 @@ 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 { - const EstablishmentsScreen({super.key}); + const EstablishmentsScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return const Center( - child: Text( - 'Établissements', - style: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold, - ), + print("Affichage de l'écran des établissements."); + + return Scaffold( + appBar: AppBar( + title: const Text('Établissements'), + backgroundColor: Colors.blueAccent, + ), + 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 + }, + ); + }, ), ); } diff --git a/lib/presentation/screens/event/event_card.dart b/lib/presentation/screens/event/event_card.dart index 2feba24..74a2952 100644 --- a/lib/presentation/screens/event/event_card.dart +++ b/lib/presentation/screens/event/event_card.dart @@ -1,429 +1,172 @@ import 'package:flutter/material.dart'; -import 'package:afterwork/data/datasources/event_remote_data_source.dart'; - -import '../../../core/utils/date_formatter.dart'; +import 'package:afterwork/data/models/event_model.dart'; +import 'package:afterwork/core/utils/date_formatter.dart'; // Importer DateFormatter /// 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 { - // Identifiant unique de l'événement - final String eventId; - // Source de données distante pour les opérations sur l'événement - final EventRemoteDataSource eventRemoteDataSource; - // Identifiant de l'utilisateur + final EventModel event; final String userId; - // Nom de l'utilisateur final String userName; - // Prénom de l'utilisateur 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; - // Callback pour l'action "Commenter" final VoidCallback onComment; - // Callback pour l'action "Partager" final VoidCallback onShare; - // Callback pour l'action "Participer" final VoidCallback onParticipate; - // Callback pour afficher plus d'options - final VoidCallback onMoreOptions; - // Callback pour fermer l'événement final VoidCallback onCloseEvent; - // Callback pour rouvrir l'événement final VoidCallback onReopenEvent; const EventCard({ Key? key, - required this.eventId, - required this.eventRemoteDataSource, + required this.event, required this.userId, required this.userName, 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.onComment, required this.onShare, required this.onParticipate, - required this.onMoreOptions, required this.onCloseEvent, required this.onReopenEvent, }) : super(key: key); @override Widget build(BuildContext context) { - // Log du rendu de la carte d'événement - print('Rendu de l\'EventCard pour l\'événement $eventId avec statut $eventStatus'); - - return AnimatedOpacity( - opacity: 1.0, - duration: const Duration(milliseconds: 300), - child: Dismissible( - key: ValueKey(eventId), - 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( + return Card( + color: const Color(0xFF2C2C3E), + margin: const EdgeInsets.symmetric(vertical: 10.0), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Card( - color: const Color(0xFF2C2C3E), - margin: const EdgeInsets.symmetric(vertical: 10.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15.0), - ), - 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(), - ], - ), - ), + _buildHeader(), + const SizedBox(height: 10), + Text( + event.title, + style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold), ), + 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. - /// Cette méthode affiche l'image de profil, le nom de l'utilisateur, la date - /// de publication de l'événement, et le statut de l'événement. - Widget _buildHeader(BuildContext context) { - // Log du rendu de l'en-tête de la carte - print('Rendu de l\'en-tête pour l\'événement $eventId'); - // Convertir la date `datePosted` en DateTime si ce n'est pas déjà fait - DateTime dateTimePosted = DateTime.parse(datePosted); + Widget _buildHeader() { + // Convertir la date de l'événement (de String à DateTime) + DateTime? eventDate; + try { + eventDate = DateTime.parse(event.startDate); + } catch (e) { + eventDate = null; // Gérer le cas où la date ne serait pas valide + } - // Utiliser le DateFormatter pour formater la date - String formattedDate = DateFormatter.formatDate(dateTimePosted); + // Utiliser DateFormatter pour afficher une date lisible si elle est valide + String formattedDate = eventDate != null ? DateFormatter.formatDate(eventDate) : 'Date inconnue'; return Row( children: [ - CircleAvatar( - backgroundImage: AssetImage(profileImage), - radius: 25, - ), + CircleAvatar(backgroundImage: NetworkImage(event.imageUrl ?? 'lib/assets/images/placeholder.png')), const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - name, - style: const TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - 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 - ], - ), - ], - ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '$userName $userLastName', + style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 5), + Text( + formattedDate, // Utiliser la date formatée ici + style: const TextStyle(color: Colors.white70, fontSize: 14), + ), + ], ), + const Spacer(), IconButton( icon: const Icon(Icons.more_vert, color: Colors.white), onPressed: () { - // Log du déclenchement du bouton "Plus d'options" - print('Plus d\'options déclenché pour l\'événement $eventId'); - onMoreOptions(); + // Logique d'affichage d'options supplémentaires pour l'événement. + // Vous pouvez utiliser un menu déroulant ou une boîte de dialogue ici. }, ), - 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() { - // Log du rendu de l'image de l'événement - print('Affichage de l\'image pour l\'événement $eventId'); - return ClipRRect( borderRadius: BorderRadius.circular(10.0), child: Image.network( - eventImageUrl, - height: 180, - width: double.infinity, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - // Log de l'erreur lors du chargement de l'image - 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, - ); - }, + event.imageUrl ?? 'lib/assets/images/placeholder.png', + height: 180, + width: double.infinity, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Image.asset('lib/assets/images/placeholder.png'); // Image par défaut si erreur de chargement + } ), ); } - /// 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() { - // Log du rendu de la ligne d'interaction de l'événement - print('Affichage des interactions pour l\'événement $eventId'); - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), // Réduire le padding vertical - child: Row( - 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(); - }, - ), - ), - ], - ), + return Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildIconButton(Icons.thumb_up_alt_outlined, 'Réagir', event.reactionsCount, onReact), + _buildIconButton(Icons.comment_outlined, 'Commenter', event.commentsCount, onComment), + _buildIconButton(Icons.share_outlined, 'Partager', event.sharesCount, onShare), + ], ); } - /// Bouton d'interaction personnalisé. - /// 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'); - + Widget _buildIconButton(IconData icon, String label, int count, VoidCallback onPressed) { return TextButton.icon( onPressed: onPressed, icon: Icon(icon, color: const Color(0xFF1DBF73), size: 20), label: Text( '$label ($count)', style: const TextStyle(color: Colors.white70, fontSize: 12), - overflow: TextOverflow.ellipsis, ), ); } - /// Bouton pour participer à l'événement. - /// Cette méthode construit un bouton qui permet de participer à l'événement. - /// Si l'événement est fermé, le bouton est caché. - Widget _buildParticipateButton() { - // Log de la construction du bouton "Participer" - print('Construction du bouton "Participer" pour l\'événement $eventId avec statut $eventStatus'); - - // Si l'événement est fermé, ne rien retourner (pas de bouton) - if (eventStatus == 'CLOSED') { - print('L\'événement $eventId est fermé, le bouton "Participer" est caché.'); - return SizedBox.shrink(); // Retourne un widget vide pour ne pas occuper d'espace - } - - // Sinon, retourner le bouton "Participer" - return ElevatedButton( - onPressed: onParticipate, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF1DBF73), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), + // Widget pour afficher le statut de l'événement et les actions associées (fermer, réouvrir) + Widget _buildStatusAndActions() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + event.status == 'closed' ? 'Événement fermé' : 'Événement ouvert', + style: TextStyle( + color: event.status == 'closed' ? Colors.red : Colors.green, + fontSize: 14, + fontWeight: FontWeight.bold, + ), ), - padding: const EdgeInsets.symmetric(vertical: 12.0), - minimumSize: const Size(double.infinity, 40), - ), - child: const Text( - 'Participer', - style: TextStyle(color: Colors.white), - ), - ); - } - - /// 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, + event.status == 'closed' + ? ElevatedButton( + onPressed: onReopenEvent, + child: const Text('Rouvrir l\'événement'), + ) + : ElevatedButton( + onPressed: onCloseEvent, + child: const Text('Fermer l\'événement'), ), - ), + ], ); } } diff --git a/lib/presentation/screens/event/event_screen.dart b/lib/presentation/screens/event/event_screen.dart index ff0c5f6..8a88f0d 100644 --- a/lib/presentation/screens/event/event_screen.dart +++ b/lib/presentation/screens/event/event_screen.dart @@ -1,19 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:afterwork/data/models/event_model.dart'; -import 'package:afterwork/data/datasources/event_remote_data_source.dart'; -import 'event_card.dart'; +import 'package:afterwork/presentation/screens/event/event_card.dart'; +import '../../state_management/event_bloc.dart'; import '../dialogs/add_event_dialog.dart'; -/// Écran principal pour afficher les événements. class EventScreen extends StatefulWidget { - final EventRemoteDataSource eventRemoteDataSource; final String userId; - final String userName; // Nom de l'utilisateur - final String userLastName; // Prénom de l'utilisateur + final String userName; + final String userLastName; const EventScreen({ Key? key, - required this.eventRemoteDataSource, required this.userId, required this.userName, required this.userLastName, @@ -24,13 +22,11 @@ class EventScreen extends StatefulWidget { } class _EventScreenState extends State { - late Future> _eventsFuture; - @override void initState() { super.initState(); - // Récupérer la liste des événements à partir de la source de données distante - _eventsFuture = widget.eventRemoteDataSource.getAllEvents(); + // Charger les événements lors de l'initialisation + context.read().add(LoadEvents(widget.userId)); } @override @@ -60,161 +56,98 @@ class _EventScreenState extends State { ); if (eventData != null) { - try { - print('Tentative de création d\'un nouvel événement par l\'utilisateur ${widget.userId}'); - await widget.eventRemoteDataSource.createEvent(eventData as EventModel); - ScaffoldMessenger.of(context).showSnackBar( - 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')), - ); - } + // Ajouter l'événement en appelant l'API via le bloc + context.read().add(AddEvent(EventModel.fromJson(eventData))); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Événement ajouté avec succès !')), + ); } }, ), ], ), - body: FutureBuilder>( - future: _eventsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { + body: BlocBuilder( + builder: (context, state) { + if (state is EventLoading) { return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - print('Erreur lors de la récupération des événements: ${snapshot.error}'); - return Center(child: Text('Erreur: ${snapshot.error}')); - } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return const Center(child: Text('Aucun événement trouvé.')); + } else if (state is EventLoaded) { + final events = state.events; + if (events.isEmpty) { + return const Center(child: Text('Aucun événement disponible.')); + } + 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}')); } - - 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), - ); - }, - ); + return const Center(child: Text('Aucun événement disponible.')); }, ), backgroundColor: const Color(0xFF1E1E2C), + floatingActionButton: FloatingActionButton( + onPressed: () { + // Recharger les événements + context.read().add(LoadEvents(widget.userId)); + }, + backgroundColor: const Color(0xFF1DBF73), + child: const Icon(Icons.refresh), + ), ); } - /// Logique pour fermer un événement - void _onCloseEvent(BuildContext context, String eventId, int index) async { - try { - 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')), - ); - } + void _onReact(String eventId) { + print('Réaction à l\'événement $eventId'); + // Implémentez la logique pour réagir à un événement ici } - /// Logique pour rouvrir un événement - void _onReopenEvent(BuildContext context, String eventId, int index) async { - try { - print('Tentative de réouverture de l\'événement $eventId'); - await widget.eventRemoteDataSource.reopenEvent(eventId); - print('Événement rouvert avec succès'); + void _onComment(String eventId) { + print('Commentaire sur l\'événement $eventId'); + // Implémentez la logique pour commenter un événement ici + } - // Montrer un message de succès - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('L\'événement a été rouvert avec succès.')), - ); + void _onShare(String eventId) { + print('Partage de l\'événement $eventId'); + // Implémentez la logique pour partager un événement ici + } - // Mettre à jour le statut de l'événement dans la liste des événements - setState(() { - _eventsFuture = _eventsFuture.then((events) { - 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' - ); + void _onParticipate(String eventId) { + print('Participation à l\'événement $eventId'); + // Implémentez la logique pour participer à un événement ici + } - // Remplacer l'événement dans la liste - events[index] = updatedEvent; - return events; - }); - }); - } catch (e) { - 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 _onCloseEvent(String eventId) { + print('Fermeture de l\'événement $eventId'); + // Appeler le bloc pour fermer l'événement + context.read().add(CloseEvent(eventId)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('L\'événement a été fermé avec succès.')), + ); + } + + void _onReopenEvent(String eventId) { + print('Réouverture de l\'événement $eventId'); + // Appeler le bloc pour rouvrir l'événement + context.read().add(ReopenEvent(eventId)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('L\'événement a été rouvert avec succès.')), + ); } } diff --git a/lib/presentation/screens/home/home_content.dart b/lib/presentation/screens/home/home_content.dart index 825e98d..40ca158 100644 --- a/lib/presentation/screens/home/home_content.dart +++ b/lib/presentation/screens/home/home_content.dart @@ -1,19 +1,163 @@ 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 { const HomeContentScreen({super.key}); @override Widget build(BuildContext context) { - return const Center( - child: Text( - 'Accueil', - style: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold, + final size = MediaQuery.of(context).size; + + return SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 15.0), // Marges réduites + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Section de bienvenue + _buildWelcomeCard(), + + const SizedBox(height: 15), // Espacement vertical réduit + + // Section "Moments populaires" + _buildCard( + context: context, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SectionHeader( + title: 'Moments populaires', + icon: Icons.camera_alt, + textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), // Taille ajustée + ), + const SizedBox(height: 10), // Espace vertical réduit + StorySection(size: size), + ], + ), + ), + + const SizedBox(height: 15), // Espacement réduit + + // Section des événements recommandés + _buildCard( + context: context, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SectionHeader( + title: 'Événements recommandés', + icon: Icons.star, + textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 10), // Espacement réduit + RecommendedEventList(size: size), + ], + ), + ), + + const SizedBox(height: 15), // Espacement réduit + + // Section des activités populaires + _buildCard( + context: context, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SectionHeader( + title: 'Activités populaires', + icon: Icons.local_activity, + textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 10), // Espacement réduit + PopularActivityList(size: size), + ], + ), + ), + + const SizedBox(height: 15), // Espacement réduit + + // Section des groupes sociaux + _buildCard( + context: context, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SectionHeader( + title: 'Groupes à rejoindre', + icon: Icons.group_add, + textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 10), // Espacement réduit + GroupList(size: size), + ], + ), + ), + + const SizedBox(height: 15), // Espacement réduit + + // Section des suggestions d'amis + _buildCard( + context: context, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SectionHeader( + title: 'Suggestions d’amis', + icon: Icons.person_add, + textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 10), // Espacement réduit + FriendSuggestions(size: size), + ], + ), + ), + ], + ), + ); + } + + // Widget pour la carte de bienvenue + Widget _buildWelcomeCard() { + return Card( + elevation: 5, + color: AppColors.surface, // Utilisation de la couleur dynamique pour la surface + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Bienvenue, Dahoud!', + style: TextStyle( + color: AppColors.textPrimary, // Texte dynamique + fontSize: 22, // Taille de police réduite + fontWeight: FontWeight.w600, // Poids de police ajusté + ), + ), + Icon(Icons.waving_hand, color: Colors.orange.shade300, size: 24), // Taille de l'icône ajustée + ], ), ), ); } + + // Widget générique pour créer une carte design avec des espaces optimisés + Widget _buildCard({required BuildContext context, required Widget child}) { + return Card( + elevation: 3, // Réduction de l'élévation pour un look plus épuré + color: AppColors.surface, // Utilisation de la couleur dynamique pour la surface + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), // Coins légèrement arrondis + child: Padding( + padding: const EdgeInsets.all(12.0), // Padding interne réduit pour un contenu plus compact + child: child, + ), + ); + } } diff --git a/lib/presentation/screens/home/home_screen.dart b/lib/presentation/screens/home/home_screen.dart index f1b22f5..d375a26 100644 --- a/lib/presentation/screens/home/home_screen.dart +++ b/lib/presentation/screens/home/home_screen.dart @@ -1,21 +1,22 @@ 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/profile/profile_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/home/home_content.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 { final EventRemoteDataSource eventRemoteDataSource; final String userId; final String userName; final String userLastName; + final String userProfileImage; // Ajouter un champ pour l'image de profil de l'utilisateur const HomeScreen({ Key? key, @@ -23,6 +24,7 @@ class HomeScreen extends StatefulWidget { required this.userId, required this.userName, required this.userLastName, + required this.userProfileImage, // Passer l'image de profil ici }) : super(key: key); @override @@ -35,136 +37,207 @@ class _HomeScreenState extends State with SingleTickerProviderStateM @override void initState() { super.initState(); - // Initialisation du TabController avec 5 onglets. - _tabController = TabController(length: 5, vsync: this); - debugPrint('HomeScreen initialisé avec userId: ${widget.userId}, userName: ${widget.userName}, userLastName: ${widget.userLastName}'); + _tabController = TabController(length: 6, vsync: this); // Ajouter un onglet pour les notifications } @override void dispose() { - // Nettoyage du TabController pour éviter les fuites de mémoire. _tabController.dispose(); super.dispose(); - debugPrint('HomeScreen dispose appelé'); } - /// Gestion des sélections dans le menu contextuel de l'AppBar. void _onMenuSelected(BuildContext context, String option) { switch (option) { case 'Publier': - debugPrint('Option "Publier" sélectionnée'); - // Rediriger vers la page de publication. + print('Publier sélectionné'); break; case 'Story': - debugPrint('Option "Story" sélectionnée'); - // Rediriger vers la page de création de Story. + print('Story sélectionné'); break; default: - debugPrint('Option inconnue sélectionnée: $option'); break; } } @override Widget build(BuildContext context) { + // Accès au ThemeProvider + final themeProvider = Provider.of(context); + return Scaffold( - appBar: AppBar( - backgroundColor: Colors.black, - elevation: 0, - leading: Padding( - padding: const EdgeInsets.all(8.0), - child: Image.asset( - 'lib/assets/images/logo.png', // Chemin correct de votre logo. - height: 40, - ), - ), - actions: [ - // Bouton pour ajouter du contenu (Publier, Story). - CircleAvatar( - backgroundColor: Colors.white, - radius: 18, - child: PopupMenuButton( - onSelected: (value) { - _onMenuSelected(context, value); - debugPrint('Menu contextuel sélectionné: $value'); - }, - itemBuilder: (context) => [ - const PopupMenuItem( - value: 'Publier', - child: Text('Publier'), + body: NestedScrollView( + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverAppBar( + backgroundColor: AppColors.backgroundColor, // Gère dynamiquement la couleur d'arrière-plan + floating: true, + pinned: true, + snap: true, + elevation: 2, // Réduction de l'élévation pour un design plus léger + leading: Padding( + padding: const EdgeInsets.all(4.0), // Réduction du padding + child: Image.asset( + 'lib/assets/images/logo.png', + height: 40, // Taille réduite du logo ), - const PopupMenuItem( - value: 'Story', - child: Text('Story'), + ), + actions: [ + _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), - color: Colors.white, - ), - ), - const SizedBox(width: 8), // Espacement entre les boutons. + bottom: TabBar( + controller: _tabController, + indicatorColor: AppColors.lightPrimary, // Tab active en bleu + labelStyle: const TextStyle( + 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. - CircleAvatar( - backgroundColor: Colors.white, - radius: 18, - child: IconButton( - icon: const Icon(Icons.search, color: Colors.blueAccent, size: 20), - onPressed: () { - debugPrint('Bouton Recherche appuyé'); - // Implémenter la logique de recherche ici. - }, + tabs: [ + const Tab(icon: Icon(Icons.home, size: 24), text: 'Accueil'), + const Tab(icon: Icon(Icons.event, size: 24), text: 'Événements'), + const Tab(icon: Icon(Icons.location_city, size: 24), text: 'Établissements'), + const Tab(icon: Icon(Icons.people, size: 24), text: 'Social'), + const Tab(icon: Icon(Icons.notifications, size: 24), text: 'Notifications'), + _buildProfileTab(), + ], + ), ), - ), - const SizedBox(width: 8), // Espacement entre les boutons. - - // 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( + ]; + }, + body: TabBarView( controller: _tabController, - indicatorColor: Colors.blueAccent, - labelColor: Colors.white, // Couleur du texte sélectionné. - unselectedLabelColor: Colors.grey[400], // Couleur du texte non sélectionné. - onTap: (index) { - debugPrint('Onglet sélectionné: $index'); - }, - tabs: const [ - Tab(icon: Icon(Icons.home), text: 'Accueil'), - Tab(icon: Icon(Icons.event), text: 'Événements'), - Tab(icon: Icon(Icons.location_city), text: 'Établissements'), - Tab(icon: Icon(Icons.people), text: 'Social'), - Tab(icon: Icon(Icons.person), text: 'Profil'), + children: [ + const HomeContentScreen(), + EventScreen( + userId: widget.userId, + userName: widget.userName, + userLastName: widget.userLastName, + ), + const EstablishmentsScreen(), + const SocialScreen(), + const NotificationsScreen(), + const ProfileScreen(), ], ), ), - 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: [ - const HomeContentScreen(), // Contenu de l'accueil. - EventScreen( - eventRemoteDataSource: widget.eventRemoteDataSource, - userId: widget.userId, - userName: widget.userName, - userLastName: widget.userLastName, - ), // Écran des événements. - const EstablishmentsScreen(), // Écran des établissements. - const SocialScreen(), // Écran social. - const ProfileScreen(), // Écran du profil. + CircleAvatar( + backgroundColor: AppColors.surface, + radius: 18, + child: IconButton( + icon: const Icon(Icons.notifications, color: AppColors.darkOnPrimary, size: 20), + onPressed: () { + // Rediriger vers l'écran des notifications + Navigator.push( + context, + 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); + }, + ), + ), ); } } diff --git a/lib/presentation/screens/location/location_picker_Screen.dart b/lib/presentation/screens/location/location_picker_Screen.dart index 3e16328..ce2123a 100644 --- a/lib/presentation/screens/location/location_picker_Screen.dart +++ b/lib/presentation/screens/location/location_picker_Screen.dart @@ -1,38 +1,82 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; -class LocationPickerScreen extends StatelessWidget { - const LocationPickerScreen({super.key}); +/// Écran pour la sélection d'une localisation sur une carte. +/// 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 { + 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 Widget build(BuildContext context) { + print('Affichage de l\'écran de sélection de localisation.'); + return Scaffold( appBar: AppBar( - title: const Text('Sélectionnez une localisation'), - backgroundColor: const Color(0xFF1E1E2C), + title: const Text('Sélectionnez un lieu'), + backgroundColor: Colors.blueAccent, ), - body: GoogleMap( - initialCameraPosition: const CameraPosition( - target: LatLng(48.8566, 2.3522), // Paris par défaut - zoom: 12.0, - ), - markers: { - Marker( - markerId: const MarkerId('selectedLocation'), - position: const LatLng(48.8566, 2.3522), // Position par défaut - draggable: true, - onDragEnd: (newPosition) { - print('Nouvelle position sélectionnée: $newPosition'); - Navigator.of(context).pop(newPosition); - }, - ) - }, - onTap: (position) { - print('Position tapée: $position'); - Navigator.of(context).pop(position); - }, + body: Column( + children: [ + Expanded( + child: GoogleMap( + initialCameraPosition: CameraPosition( + target: _pickedLocation, + zoom: 14, + ), + onMapCreated: (controller) { + _mapController = controller; + print('Carte Google Maps créée.'); + }, + onTap: _selectLocation, // Sélection de la localisation sur la carte + markers: { + Marker( + markerId: const MarkerId('pickedLocation'), + position: _pickedLocation, + ), + }, + ), + ), + 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), + ), + ), + ), + ], ), ); } -} \ No newline at end of file + + /// 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(); + } +} diff --git a/lib/presentation/screens/login/login_screen.dart b/lib/presentation/screens/login/login_screen.dart index 6b66eb3..40d4649 100644 --- a/lib/presentation/screens/login/login_screen.dart +++ b/lib/presentation/screens/login/login_screen.dart @@ -1,15 +1,22 @@ import 'dart:async'; -import 'package:flutter/material.dart'; import 'package:afterwork/data/datasources/user_remote_data_source.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/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 '../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 { const LoginScreen({super.key}); @@ -18,87 +25,91 @@ class LoginScreen extends StatefulWidget { } class _LoginScreenState extends State with SingleTickerProviderStateMixin { - final _formKey = GlobalKey(); - String _userId = ''; - String _email = ''; - String _password = ''; - bool _isPasswordVisible = false; - bool _isSubmitting = false; + final _formKey = GlobalKey(); // Clé pour valider le formulaire de connexion. + // 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 SecureStorage _secureStorage = SecureStorage(); final PreferencesHelper _preferencesHelper = PreferencesHelper(); - late AnimationController _controller; - late Animation _buttonScaleAnimation; + // Contrôleur pour le bouton de chargement + final _btnController = LoadingButtonController(); + + // Contrôleur d'animation pour la transition des écrans + late AnimationController _animationController; @override void initState() { super.initState(); - _controller = AnimationController( + _animationController = AnimationController( vsync: this, - duration: const Duration(milliseconds: 300), - ); - _buttonScaleAnimation = Tween(begin: 1.0, end: 1.1).animate( - CurvedAnimation(parent: _controller, curve: Curves.easeInOut), + duration: const Duration(milliseconds: 500), ); + print("Contrôleur d'animation initialisé."); } @override void dispose() { - _controller.dispose(); + _animationController.dispose(); + print("Ressources d'animation libérées."); super.dispose(); } - /// Afficher/Masquer le mot de passe + /// Fonction pour basculer la visibilité du mot de passe void _togglePasswordVisibility() { setState(() { _isPasswordVisible = !_isPasswordVisible; }); + print("Visibilité du mot de passe basculée: $_isPasswordVisible"); } - /// Soumission du formulaire d'authentification - void _submit() async { + /// Fonction pour afficher un toast via FlutterToast + 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 _submit() async { + print("Tentative de soumission du formulaire de connexion."); + if (_formKey.currentState!.validate()) { setState(() { _isSubmitting = true; + _showErrorMessage = false; }); _formKey.currentState!.save(); - print("===== DEBUT DE LA SOUMISSION DU FORMULAIRE ====="); - print("Email: $_email"); - print("Mot de passe: $_password"); - 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 - 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 + print("Utilisateur authentifié : ${user.userId}"); await _secureStorage.saveUserId(user.userId); await _preferencesHelper.saveUserName(user.nom); await _preferencesHelper.saveUserLastName(user.prenoms); + _showToast("Connexion réussie !"); - print("===== SAUVEGARDE DES DONNÉES UTILISATEUR ====="); - print("User ID: ${user.userId}"); - print("User Name: ${user.nom}"); - print("User Last Name: ${user.prenoms}"); - - // Navigation vers l'écran d'accueil + // Navigation vers la page d'accueil Navigator.pushReplacement( context, MaterialPageRoute( @@ -107,44 +118,74 @@ class _LoginScreenState extends State with SingleTickerProviderStat userId: user.userId, userName: user.nom, userLastName: user.prenoms, + userProfileImage: 'lib/assets/images/profile_picture.png', ), ), ); - print("===== NAVIGATION VERS HOME SCREEN ====="); } catch (e) { - print('Erreur lors de la connexion: $e'); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Erreur : ${e.toString()}')), - ); + print("Erreur lors de l'authentification : $e"); + _btnController.error(); + _showToast("Erreur lors de la connexion : ${e.toString()}"); + setState(() { + _showErrorMessage = true; + }); } finally { - print('Fin du processus d\'authentification'); // Débogage + _btnController.reset(); setState(() { _isSubmitting = false; }); } } else { - print("===== FORMULAIRE NON VALIDE ====="); + print("Échec de validation du formulaire."); + _btnController.reset(); + _showToast("Veuillez vérifier les informations saisies."); } } @override Widget build(BuildContext context) { + final theme = Theme.of(context); final size = MediaQuery.of(context).size; + final themeProvider = Provider.of(context); + bool isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom != 0; return Scaffold( body: Stack( children: [ - // Arrière-plan avec dégradé - Container( - decoration: const BoxDecoration( + AnimatedContainer( + duration: const Duration(seconds: 3), + decoration: BoxDecoration( gradient: LinearGradient( - colors: [Color(0xFF4A90E2), Color(0xFF9013FE)], + colors: [ + theme.colorScheme.primary, + theme.colorScheme.secondary + ], begin: Alignment.topLeft, 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( child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), @@ -153,148 +194,179 @@ class _LoginScreenState extends State with SingleTickerProviderStat child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Logo animé - AnimatedBuilder( - animation: _controller, - 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, - ), - ), + Image.asset( + 'lib/assets/images/logo.png', + height: size.height * 0.25, ), const SizedBox(height: 20), - const Text( + Text( 'Bienvenue sur AfterWork', - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.bold, - color: Colors.white, - shadows: [ - Shadow( - offset: Offset(0, 2), - blurRadius: 6, - color: Colors.black26, - ), - ], - ), + style: theme.textTheme.titleLarge, ), const SizedBox(height: 40), - // Champ Email - TextFormField( - decoration: InputDecoration( - 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), + _buildTextFormField( + label: 'Email', + icon: Icons.email, validator: (value) { if (value == null || value.isEmpty) { - print("Erreur: Le champ email est vide"); + print("Erreur : champ email vide."); return 'Veuillez entrer votre email'; } 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 null; }, onSaved: (value) { - _email = value ?? ''; // Utiliser une chaîne vide si value est null - print("Email sauvegardé: $_email"); + _email = value!; + print("Email enregistré : $_email"); }, ), const SizedBox(height: 20), - // Champ Mot de passe - TextFormField( - decoration: InputDecoration( - 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, - ), - ), + _buildTextFormField( + label: 'Mot de passe', + icon: Icons.lock, 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) { 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'; } 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 null; }, onSaved: (value) { - _password = value ?? ''; // Utiliser une chaîne vide si value est null - print("Mot de passe sauvegardé: $_password"); + _password = value!; + print("Mot de passe enregistré."); }, ), - const SizedBox(height: 20), - // Bouton de connexion avec animation de soumission + const SizedBox(height: 30), SizedBox( - width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.all(16.0), - textStyle: const TextStyle(fontSize: 18), - backgroundColor: _isSubmitting ? Colors.grey : Colors.blueAccent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), + width: size.width * 0.85, + child: LoadingButton( + controller: _btnController, + onPressed: _isSubmitting ? null : _submit, + iconData: Icons.login, + iconColor: theme.colorScheme.onPrimary, + child: Text( + '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), - // Lien pour s'inscrire TextButton( onPressed: () { - // Naviguer 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', - 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 validator, + required FormFieldSetter 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, + ); + } } diff --git a/lib/presentation/screens/notifications/notifications_screen.dart b/lib/presentation/screens/notifications/notifications_screen.dart new file mode 100644 index 0000000..3b92570 --- /dev/null +++ b/lib/presentation/screens/notifications/notifications_screen.dart @@ -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'), + ), + ); + } +} diff --git a/lib/presentation/screens/profile/profile_screen.dart b/lib/presentation/screens/profile/profile_screen.dart index d0addf4..8690e67 100644 --- a/lib/presentation/screens/profile/profile_screen.dart +++ b/lib/presentation/screens/profile/profile_screen.dart @@ -1,117 +1,147 @@ import 'package:flutter/material.dart'; +import '../../../core/constants/colors.dart'; class ProfileScreen extends StatelessWidget { const ProfileScreen({super.key}); @override Widget build(BuildContext context) { + print("Affichage de l'écran de profil."); + return Scaffold( - appBar: AppBar( - title: const Text('Profil', - style: TextStyle( - color: Color(0xFF1DBF73), // Définit la couleur verte du texte - ), - ), - backgroundColor: const Color(0xFF1E1E2C), - actions: [ - IconButton( - icon: const Icon(Icons.settings, color: Colors.white), - onPressed: () { - // Naviguer vers la page des paramètres - }, - ), - ], - ), - 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 - }, + backgroundColor: AppColors.backgroundColor, + body: CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: 200.0, + floating: false, + pinned: true, + backgroundColor: AppColors.darkPrimary, + flexibleSpace: FlexibleSpaceBar( + title: Text( + 'Profil', + style: TextStyle( + color: AppColors.accentColor, + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), ), - _buildAnimatedListTile( - icon: Icons.history, - label: 'Historique des Publications', - onTap: () { - // Naviguer vers l'historique des publications - }, + background: Image.asset( + 'lib/assets/images/profile_picture.png', + fit: BoxFit.cover, ), - _buildAnimatedListTile( - icon: Icons.bookmark, - label: 'Historique de Réservations', - onTap: () { - // Naviguer vers l'historique des réservations + ), + actions: [ + IconButton( + icon: const Icon(Icons.settings, color: Colors.white), + onPressed: () { + print("Bouton des paramètres cliqué."); + // Logique de navigation vers les paramètres }, ), ], ), - const SizedBox(height: 20), - _buildExpandableSectionCard( - title: 'Préférences et Paramètres', - icon: Icons.settings, - children: [ - _buildAnimatedListTile( - icon: Icons.privacy_tip, - label: 'Paramètres de confidentialité', - onTap: () { - // Naviguer vers les paramètres de confidentialité - }, - ), - _buildAnimatedListTile( - icon: Icons.notifications, - label: 'Notifications', - onTap: () { - // Naviguer vers les paramètres de notification - }, - ), - _buildAnimatedListTile( - icon: Icons.language, - label: 'Langue de l\'application', - onTap: () { - // Naviguer vers les paramètres de langue - }, - ), - _buildAnimatedListTile( - icon: Icons.format_paint, - label: 'Thème de l\'application', - onTap: () { - // Naviguer vers les paramètres de thème - }, - ), - ], + SliverList( + delegate: SliverChildListDelegate( + [ + const SizedBox(height: 10), + _buildUserInfoCard(), + const SizedBox(height: 10), + _buildEditOptionsCard(), + const SizedBox(height: 10), + _buildStatisticsSectionCard(), + const SizedBox(height: 10), + _buildExpandableSectionCard( + title: 'Historique', + icon: Icons.history, + children: [ + _buildAnimatedListTile( + icon: Icons.event_note, + label: 'Historique des Événements', + onTap: () { + print("Accès à l'historique des événements."); + // Logique de navigation vers l'historique des événements + }, + ), + _buildAnimatedListTile( + icon: Icons.history, + label: 'Historique des Publications', + onTap: () { + print("Accès à l'historique des publications."); + // Logique de navigation vers l'historique des publications + }, + ), + _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() { return Card( - color: const Color(0xFF292B37), + color: AppColors.cardColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + elevation: 2, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ - const CircleAvatar( + CircleAvatar( radius: 50, backgroundImage: AssetImage('lib/assets/images/profile_picture.png'), backgroundColor: Colors.transparent, @@ -152,29 +182,33 @@ class ProfileScreen extends StatelessWidget { Widget _buildEditOptionsCard() { return Card( - color: const Color(0xFF292B37), + color: AppColors.cardColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + elevation: 2, child: Column( children: [ _buildAnimatedListTile( icon: Icons.edit, label: 'Éditer le profil', onTap: () { - // Naviguer vers la page d'édition de profil + print("Édition du profil."); + // Logique de navigation vers l'édition du profil }, ), _buildAnimatedListTile( icon: Icons.camera_alt, label: 'Changer la photo de profil', 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( icon: Icons.lock, label: 'Changer le mot de passe', 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() { return Card( - color: const Color(0xFF292B37), + color: AppColors.cardColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + elevation: 2, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( @@ -232,8 +267,9 @@ class ProfileScreen extends StatelessWidget { required List children, }) { return Card( - color: const Color(0xFF292B37), + color: AppColors.cardColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + elevation: 2, child: ExpansionTile( title: Text( title, @@ -243,9 +279,9 @@ class ProfileScreen extends StatelessWidget { color: Colors.white, ), ), - leading: Icon(icon, color: const Color(0xFF1DBF73)), - iconColor: const Color(0xFF1DBF73), - collapsedIconColor: const Color(0xFF1DBF73), + leading: Icon(icon, color: AppColors.accentColor), + iconColor: AppColors.accentColor, + collapsedIconColor: AppColors.accentColor, children: children, ), ); @@ -253,8 +289,9 @@ class ProfileScreen extends StatelessWidget { Widget _buildSupportSectionCard() { return Card( - color: const Color(0xFF292B37), + color: AppColors.cardColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + elevation: 2, child: Column( children: [ const Padding( @@ -272,21 +309,24 @@ class ProfileScreen extends StatelessWidget { icon: Icons.help, label: 'Support et Assistance', onTap: () { - // Naviguer vers la page de support + print("Accès au Support et Assistance."); + // Logique de navigation vers le support }, ), _buildAnimatedListTile( icon: Icons.article, label: 'Conditions d\'utilisation', onTap: () { - // Naviguer vers les conditions d'utilisation + print("Accès aux conditions d'utilisation."); + // Logique de navigation vers les conditions d'utilisation }, ), _buildAnimatedListTile( icon: Icons.privacy_tip, label: 'Politique de confidentialité', 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) { return Card( - color: const Color(0xFF292B37), + color: AppColors.cardColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + elevation: 2, child: ListTile( leading: const Icon(Icons.delete, color: Colors.redAccent), title: const Text( @@ -307,7 +348,6 @@ class ProfileScreen extends StatelessWidget { onTap: () { _showDeleteConfirmationDialog(context); }, - hoverColor: Colors.red.withOpacity(0.1), ), ); } @@ -317,7 +357,7 @@ class ProfileScreen extends StatelessWidget { context: context, builder: (BuildContext context) { return AlertDialog( - backgroundColor: const Color(0xFF1E1E2C), + backgroundColor: AppColors.backgroundColor, title: const Text( 'Confirmer la suppression', style: TextStyle(color: Colors.white), @@ -329,17 +369,17 @@ class ProfileScreen extends StatelessWidget { actions: [ TextButton( onPressed: () { - Navigator.of(context).pop(); // Fermer le popup + Navigator.of(context).pop(); }, - child: const Text( + child: Text( 'Annuler', - style: TextStyle(color: Color(0xFF1DBF73)), + style: TextStyle(color: AppColors.accentColor), ), ), TextButton( onPressed: () { - // Logique de suppression du compte ici - Navigator.of(context).pop(); // Fermer le popup après la suppression + print("Suppression du compte confirmée."); + Navigator.of(context).pop(); }, child: const Text( 'Supprimer', @@ -362,12 +402,11 @@ class ProfileScreen extends StatelessWidget { borderRadius: BorderRadius.circular(10), splashColor: Colors.blueAccent.withOpacity(0.2), child: ListTile( - leading: Icon(icon, color: const Color(0xFF1DBF73)), + leading: Icon(icon, color: AppColors.accentColor), title: Text( label, 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, }) { return ListTile( - leading: Icon(icon, color: const Color(0xFF1DBF73)), + leading: Icon(icon, color: AppColors.accentColor), title: Text(label, style: const TextStyle(color: Colors.white)), trailing: Text( value, @@ -388,7 +427,6 @@ class ProfileScreen extends StatelessWidget { fontSize: 16, ), ), - hoverColor: Colors.blue.withOpacity(0.1), ); } } diff --git a/lib/presentation/screens/signup/SignUpScreen.dart b/lib/presentation/screens/signup/SignUpScreen.dart new file mode 100644 index 0000000..be3d741 --- /dev/null +++ b/lib/presentation/screens/signup/SignUpScreen.dart @@ -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 { + final _formKey = GlobalKey(); // 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 _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(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 validator, + required FormFieldSetter 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, + ); + } +} diff --git a/lib/presentation/state_management/event_bloc.dart b/lib/presentation/state_management/event_bloc.dart index c1de968..1192f7b 100644 --- a/lib/presentation/state_management/event_bloc.dart +++ b/lib/presentation/state_management/event_bloc.dart @@ -7,7 +7,10 @@ import 'package:afterwork/data/datasources/event_remote_data_source.dart'; @immutable abstract class EventEvent {} -class LoadEvents extends EventEvent {} +class LoadEvents extends EventEvent { + final String userId; + LoadEvents(this.userId); +} class AddEvent extends EventEvent { final EventModel event; @@ -15,6 +18,18 @@ class AddEvent extends EventEvent { 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 @immutable abstract class EventState {} @@ -35,39 +50,61 @@ class EventError extends EventState { EventError(this.message); } -// Bloc principal pour gérer la logique des événements +// Bloc pour la gestion des événements class EventBloc extends Bloc { final EventRemoteDataSource remoteDataSource; EventBloc({required this.remoteDataSource}) : super(EventInitial()) { on(_onLoadEvents); on(_onAddEvent); + on(_onCloseEvent); + on(_onReopenEvent); } - // Gestion de l'événement LoadEvents + // Gestion du chargement des événements Future _onLoadEvents(LoadEvents event, Emitter emit) async { emit(EventLoading()); try { final events = await remoteDataSource.getAllEvents(); emit(EventLoaded(events)); - print('Événements chargés avec succès.'); } catch (e) { 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 _onAddEvent(AddEvent event, Emitter emit) async { emit(EventLoading()); try { await remoteDataSource.createEvent(event.event); final events = await remoteDataSource.getAllEvents(); emit(EventLoaded(events)); - print('Événement ajouté avec succès.'); } catch (e) { 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 _onCloseEvent(CloseEvent event, Emitter 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 _onReopenEvent(ReopenEvent event, Emitter 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.')); } } } diff --git a/lib/presentation/state_management/user_bloc.dart b/lib/presentation/state_management/user_bloc.dart index b1cc3ab..a11db15 100644 --- a/lib/presentation/state_management/user_bloc.dart +++ b/lib/presentation/state_management/user_bloc.dart @@ -2,42 +2,52 @@ import 'package:afterwork/domain/entities/user.dart'; import 'package:afterwork/domain/usecases/get_user.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 { final GetUser getUser; + /// Constructeur avec injection du cas d'utilisation `GetUser`. UserBloc({required this.getUser}) : super(UserInitial()); + @override Stream mapEventToState(UserEvent event) async* { if (event is GetUserById) { yield UserLoading(); final either = await getUser(event.id); yield either.fold( - (failure) => UserError(), - (user) => UserLoaded(user: user), + (failure) => UserError(), + (user) => UserLoaded(user: user), ); } } } +/// Classe abstraite représentant les événements liés à l'utilisateur. abstract class UserEvent {} +/// Événement pour récupérer un utilisateur par son ID. class GetUserById extends UserEvent { final String id; GetUserById(this.id); } +/// Classe abstraite représentant les états possibles du BLoC utilisateur. abstract class UserState {} +/// État initial lorsque rien n'est encore chargé. class UserInitial extends UserState {} +/// État indiquant que les données utilisateur sont en cours de chargement. class UserLoading extends UserState {} +/// État indiquant que les données utilisateur ont été chargées avec succès. class UserLoaded extends UserState { final User 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 {} diff --git a/lib/presentation/widgets/animated_action_button.dart b/lib/presentation/widgets/animated_action_button.dart new file mode 100644 index 0000000..53fb0fb --- /dev/null +++ b/lib/presentation/widgets/animated_action_button.dart @@ -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)), + ], + ); + } +} diff --git a/lib/presentation/widgets/create_story.dart b/lib/presentation/widgets/create_story.dart new file mode 100644 index 0000000..4e33561 --- /dev/null +++ b/lib/presentation/widgets/create_story.dart @@ -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 { + 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.'); + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/custom_drawer.dart b/lib/presentation/widgets/custom_drawer.dart index 774bb63..7ad7d02 100644 --- a/lib/presentation/widgets/custom_drawer.dart +++ b/lib/presentation/widgets/custom_drawer.dart @@ -7,7 +7,7 @@ class CustomDrawer extends StatelessWidget { child: ListView( padding: EdgeInsets.zero, children: [ - DrawerHeader( + const DrawerHeader( decoration: BoxDecoration( color: Colors.blueAccent, ), diff --git a/lib/presentation/widgets/event_list.dart b/lib/presentation/widgets/event_list.dart new file mode 100644 index 0000000..6a608d1 --- /dev/null +++ b/lib/presentation/widgets/event_list.dart @@ -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 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'); + } +} diff --git a/lib/presentation/widgets/friend_suggestions.dart b/lib/presentation/widgets/friend_suggestions.dart new file mode 100644 index 0000000..813117f --- /dev/null +++ b/lib/presentation/widgets/friend_suggestions.dart @@ -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'), + ), + ], + ), + ); + }), + ); + } +} diff --git a/lib/presentation/widgets/group_list.dart b/lib/presentation/widgets/group_list.dart new file mode 100644 index 0000000..7f84d59 --- /dev/null +++ b/lib/presentation/widgets/group_list.dart @@ -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'), + ), + ], + ), + ); + }), + ); + } +} diff --git a/lib/presentation/widgets/popular_activity_list.dart b/lib/presentation/widgets/popular_activity_list.dart new file mode 100644 index 0000000..5986d43 --- /dev/null +++ b/lib/presentation/widgets/popular_activity_list.dart @@ -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, + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/widgets/quick_action_button.dart b/lib/presentation/widgets/quick_action_button.dart new file mode 100644 index 0000000..79140db --- /dev/null +++ b/lib/presentation/widgets/quick_action_button.dart @@ -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, + ), + ], + ); + } +} diff --git a/lib/presentation/widgets/recommended_event_list.dart b/lib/presentation/widgets/recommended_event_list.dart new file mode 100644 index 0000000..1642d56 --- /dev/null +++ b/lib/presentation/widgets/recommended_event_list.dart @@ -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, + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/widgets/section_header.dart b/lib/presentation/widgets/section_header.dart new file mode 100644 index 0000000..3efa74d --- /dev/null +++ b/lib/presentation/widgets/section_header.dart @@ -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), + ], + ); + } +} diff --git a/lib/presentation/widgets/story_detail.dart b/lib/presentation/widgets/story_detail.dart new file mode 100644 index 0000000..937ffec --- /dev/null +++ b/lib/presentation/widgets/story_detail.dart @@ -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 { + 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'), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/widgets/story_section.dart b/lib/presentation/widgets/story_section.dart new file mode 100644 index 0000000..64b480b --- /dev/null +++ b/lib/presentation/widgets/story_section.dart @@ -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 + ), + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/story_video_player.dart b/lib/presentation/widgets/story_video_player.dart new file mode 100644 index 0000000..26a1699 --- /dev/null +++ b/lib/presentation/widgets/story_video_player.dart @@ -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 { + 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'), + ), + ], + ), + ); + } +} diff --git a/libs/arm64-v8a/libargon2.so b/libs/arm64-v8a/libargon2.so new file mode 100644 index 0000000..08cf61c Binary files /dev/null and b/libs/arm64-v8a/libargon2.so differ diff --git a/libs/armeabi-v7a/libargon2.so b/libs/armeabi-v7a/libargon2.so new file mode 100644 index 0000000..68c1fb3 Binary files /dev/null and b/libs/armeabi-v7a/libargon2.so differ diff --git a/libs/x86/libargon2.so b/libs/x86/libargon2.so new file mode 100644 index 0000000..0b74b70 Binary files /dev/null and b/libs/x86/libargon2.so differ diff --git a/libs/x86_64/libargon2.so b/libs/x86_64/libargon2.so new file mode 100644 index 0000000..1cff68c Binary files /dev/null and b/libs/x86_64/libargon2.so differ diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d0e7f79..85a2413 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include 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 = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b29e9ba..62e3ed5 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux flutter_secure_storage_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index e6a54de..c4384c1 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,16 @@ import FlutterMacOS import Foundation +import file_selector_macos import flutter_secure_storage_macos +import path_provider_foundation import shared_preferences_foundation +import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/native_libs/argon2.dll b/native_libs/argon2.dll new file mode 100644 index 0000000..7a0e491 Binary files /dev/null and b/native_libs/argon2.dll differ diff --git a/obj/local/arm64-v8a/libargon2.so b/obj/local/arm64-v8a/libargon2.so new file mode 100644 index 0000000..07a4e36 Binary files /dev/null and b/obj/local/arm64-v8a/libargon2.so differ diff --git a/obj/local/arm64-v8a/objs/argon2/src/argon2.o b/obj/local/arm64-v8a/objs/argon2/src/argon2.o new file mode 100644 index 0000000..51d2626 Binary files /dev/null and b/obj/local/arm64-v8a/objs/argon2/src/argon2.o differ diff --git a/obj/local/arm64-v8a/objs/argon2/src/argon2.o.d b/obj/local/arm64-v8a/objs/argon2/src/argon2.o.d new file mode 100644 index 0000000..ae4ce1f --- /dev/null +++ b/obj/local/arm64-v8a/objs/argon2/src/argon2.o.d @@ -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: diff --git a/obj/local/arm64-v8a/objs/argon2/src/blake2b.o b/obj/local/arm64-v8a/objs/argon2/src/blake2b.o new file mode 100644 index 0000000..3d1d8e3 Binary files /dev/null and b/obj/local/arm64-v8a/objs/argon2/src/blake2b.o differ diff --git a/obj/local/arm64-v8a/objs/argon2/src/blake2b.o.d b/obj/local/arm64-v8a/objs/argon2/src/blake2b.o.d new file mode 100644 index 0000000..cad3c86 --- /dev/null +++ b/obj/local/arm64-v8a/objs/argon2/src/blake2b.o.d @@ -0,0 +1,7 @@ +./obj/local/arm64-v8a/objs/argon2/src/blake2b.o: \ + android/app/src/main/jni/src/blake2b.c \ + android/app/src/main/jni/blake2.h android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/blake2-impl.h +android/app/src/main/jni/blake2.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/blake2-impl.h: diff --git a/obj/local/arm64-v8a/objs/argon2/src/core.o b/obj/local/arm64-v8a/objs/argon2/src/core.o new file mode 100644 index 0000000..baa3593 Binary files /dev/null and b/obj/local/arm64-v8a/objs/argon2/src/core.o differ diff --git a/obj/local/arm64-v8a/objs/argon2/src/core.o.d b/obj/local/arm64-v8a/objs/argon2/src/core.o.d new file mode 100644 index 0000000..f729b59 --- /dev/null +++ b/obj/local/arm64-v8a/objs/argon2/src/core.o.d @@ -0,0 +1,10 @@ +./obj/local/arm64-v8a/objs/argon2/src/core.o: \ + android/app/src/main/jni/src/core.c android/app/src/main/jni/core.h \ + android/app/src/main/jni/argon2.h android/app/src/main/jni/thread.h \ + android/app/src/main/jni/src/blake2/blake2.h \ + android/app/src/main/jni/src/blake2/blake2-impl.h +android/app/src/main/jni/core.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/thread.h: +android/app/src/main/jni/src/blake2/blake2.h: +android/app/src/main/jni/src/blake2/blake2-impl.h: diff --git a/obj/local/arm64-v8a/objs/argon2/src/encoding.o b/obj/local/arm64-v8a/objs/argon2/src/encoding.o new file mode 100644 index 0000000..568722f Binary files /dev/null and b/obj/local/arm64-v8a/objs/argon2/src/encoding.o differ diff --git a/obj/local/arm64-v8a/objs/argon2/src/encoding.o.d b/obj/local/arm64-v8a/objs/argon2/src/encoding.o.d new file mode 100644 index 0000000..31c2c0a --- /dev/null +++ b/obj/local/arm64-v8a/objs/argon2/src/encoding.o.d @@ -0,0 +1,7 @@ +./obj/local/arm64-v8a/objs/argon2/src/encoding.o: \ + android/app/src/main/jni/src/encoding.c \ + android/app/src/main/jni/encoding.h android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/core.h +android/app/src/main/jni/encoding.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/core.h: diff --git a/obj/local/arm64-v8a/objs/argon2/src/ref.o b/obj/local/arm64-v8a/objs/argon2/src/ref.o new file mode 100644 index 0000000..7ee3bac Binary files /dev/null and b/obj/local/arm64-v8a/objs/argon2/src/ref.o differ diff --git a/obj/local/arm64-v8a/objs/argon2/src/ref.o.d b/obj/local/arm64-v8a/objs/argon2/src/ref.o.d new file mode 100644 index 0000000..89072df --- /dev/null +++ b/obj/local/arm64-v8a/objs/argon2/src/ref.o.d @@ -0,0 +1,11 @@ +./obj/local/arm64-v8a/objs/argon2/src/ref.o: \ + android/app/src/main/jni/src/ref.c android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/core.h \ + android/app/src/main/jni/src/blake2/blamka-round-ref.h \ + android/app/src/main/jni/src/blake2/blake2.h \ + android/app/src/main/jni/src/blake2/blake2-impl.h +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/core.h: +android/app/src/main/jni/src/blake2/blamka-round-ref.h: +android/app/src/main/jni/src/blake2/blake2.h: +android/app/src/main/jni/src/blake2/blake2-impl.h: diff --git a/obj/local/arm64-v8a/objs/argon2/src/thread.o b/obj/local/arm64-v8a/objs/argon2/src/thread.o new file mode 100644 index 0000000..a39511f Binary files /dev/null and b/obj/local/arm64-v8a/objs/argon2/src/thread.o differ diff --git a/obj/local/arm64-v8a/objs/argon2/src/thread.o.d b/obj/local/arm64-v8a/objs/argon2/src/thread.o.d new file mode 100644 index 0000000..e32c1e3 --- /dev/null +++ b/obj/local/arm64-v8a/objs/argon2/src/thread.o.d @@ -0,0 +1,4 @@ +./obj/local/arm64-v8a/objs/argon2/src/thread.o: \ + android/app/src/main/jni/src/thread.c \ + android/app/src/main/jni/thread.h +android/app/src/main/jni/thread.h: diff --git a/obj/local/armeabi-v7a/libargon2.so b/obj/local/armeabi-v7a/libargon2.so new file mode 100644 index 0000000..773dff8 Binary files /dev/null and b/obj/local/armeabi-v7a/libargon2.so differ diff --git a/obj/local/armeabi-v7a/objs/argon2/src/argon2.o b/obj/local/armeabi-v7a/objs/argon2/src/argon2.o new file mode 100644 index 0000000..887412e Binary files /dev/null and b/obj/local/armeabi-v7a/objs/argon2/src/argon2.o differ diff --git a/obj/local/armeabi-v7a/objs/argon2/src/argon2.o.d b/obj/local/armeabi-v7a/objs/argon2/src/argon2.o.d new file mode 100644 index 0000000..d741fc2 --- /dev/null +++ b/obj/local/armeabi-v7a/objs/argon2/src/argon2.o.d @@ -0,0 +1,7 @@ +./obj/local/armeabi-v7a/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: diff --git a/obj/local/armeabi-v7a/objs/argon2/src/blake2b.o b/obj/local/armeabi-v7a/objs/argon2/src/blake2b.o new file mode 100644 index 0000000..1c6fc6a Binary files /dev/null and b/obj/local/armeabi-v7a/objs/argon2/src/blake2b.o differ diff --git a/obj/local/armeabi-v7a/objs/argon2/src/blake2b.o.d b/obj/local/armeabi-v7a/objs/argon2/src/blake2b.o.d new file mode 100644 index 0000000..8b16dcf --- /dev/null +++ b/obj/local/armeabi-v7a/objs/argon2/src/blake2b.o.d @@ -0,0 +1,7 @@ +./obj/local/armeabi-v7a/objs/argon2/src/blake2b.o: \ + android/app/src/main/jni/src/blake2b.c \ + android/app/src/main/jni/blake2.h android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/blake2-impl.h +android/app/src/main/jni/blake2.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/blake2-impl.h: diff --git a/obj/local/armeabi-v7a/objs/argon2/src/core.o b/obj/local/armeabi-v7a/objs/argon2/src/core.o new file mode 100644 index 0000000..da69b0b Binary files /dev/null and b/obj/local/armeabi-v7a/objs/argon2/src/core.o differ diff --git a/obj/local/armeabi-v7a/objs/argon2/src/core.o.d b/obj/local/armeabi-v7a/objs/argon2/src/core.o.d new file mode 100644 index 0000000..8d47059 --- /dev/null +++ b/obj/local/armeabi-v7a/objs/argon2/src/core.o.d @@ -0,0 +1,10 @@ +./obj/local/armeabi-v7a/objs/argon2/src/core.o: \ + android/app/src/main/jni/src/core.c android/app/src/main/jni/core.h \ + android/app/src/main/jni/argon2.h android/app/src/main/jni/thread.h \ + android/app/src/main/jni/src/blake2/blake2.h \ + android/app/src/main/jni/src/blake2/blake2-impl.h +android/app/src/main/jni/core.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/thread.h: +android/app/src/main/jni/src/blake2/blake2.h: +android/app/src/main/jni/src/blake2/blake2-impl.h: diff --git a/obj/local/armeabi-v7a/objs/argon2/src/encoding.o b/obj/local/armeabi-v7a/objs/argon2/src/encoding.o new file mode 100644 index 0000000..5f50fd6 Binary files /dev/null and b/obj/local/armeabi-v7a/objs/argon2/src/encoding.o differ diff --git a/obj/local/armeabi-v7a/objs/argon2/src/encoding.o.d b/obj/local/armeabi-v7a/objs/argon2/src/encoding.o.d new file mode 100644 index 0000000..d8ca2db --- /dev/null +++ b/obj/local/armeabi-v7a/objs/argon2/src/encoding.o.d @@ -0,0 +1,7 @@ +./obj/local/armeabi-v7a/objs/argon2/src/encoding.o: \ + android/app/src/main/jni/src/encoding.c \ + android/app/src/main/jni/encoding.h android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/core.h +android/app/src/main/jni/encoding.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/core.h: diff --git a/obj/local/armeabi-v7a/objs/argon2/src/ref.o b/obj/local/armeabi-v7a/objs/argon2/src/ref.o new file mode 100644 index 0000000..f1b94fb Binary files /dev/null and b/obj/local/armeabi-v7a/objs/argon2/src/ref.o differ diff --git a/obj/local/armeabi-v7a/objs/argon2/src/ref.o.d b/obj/local/armeabi-v7a/objs/argon2/src/ref.o.d new file mode 100644 index 0000000..182f9b2 --- /dev/null +++ b/obj/local/armeabi-v7a/objs/argon2/src/ref.o.d @@ -0,0 +1,11 @@ +./obj/local/armeabi-v7a/objs/argon2/src/ref.o: \ + android/app/src/main/jni/src/ref.c android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/core.h \ + android/app/src/main/jni/src/blake2/blamka-round-ref.h \ + android/app/src/main/jni/src/blake2/blake2.h \ + android/app/src/main/jni/src/blake2/blake2-impl.h +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/core.h: +android/app/src/main/jni/src/blake2/blamka-round-ref.h: +android/app/src/main/jni/src/blake2/blake2.h: +android/app/src/main/jni/src/blake2/blake2-impl.h: diff --git a/obj/local/armeabi-v7a/objs/argon2/src/thread.o b/obj/local/armeabi-v7a/objs/argon2/src/thread.o new file mode 100644 index 0000000..78878d1 Binary files /dev/null and b/obj/local/armeabi-v7a/objs/argon2/src/thread.o differ diff --git a/obj/local/armeabi-v7a/objs/argon2/src/thread.o.d b/obj/local/armeabi-v7a/objs/argon2/src/thread.o.d new file mode 100644 index 0000000..2d50402 --- /dev/null +++ b/obj/local/armeabi-v7a/objs/argon2/src/thread.o.d @@ -0,0 +1,4 @@ +./obj/local/armeabi-v7a/objs/argon2/src/thread.o: \ + android/app/src/main/jni/src/thread.c \ + android/app/src/main/jni/thread.h +android/app/src/main/jni/thread.h: diff --git a/obj/local/riscv64/objs/argon2/src/argon2.o b/obj/local/riscv64/objs/argon2/src/argon2.o new file mode 100644 index 0000000..2000afb Binary files /dev/null and b/obj/local/riscv64/objs/argon2/src/argon2.o differ diff --git a/obj/local/riscv64/objs/argon2/src/argon2.o.d b/obj/local/riscv64/objs/argon2/src/argon2.o.d new file mode 100644 index 0000000..7989fae --- /dev/null +++ b/obj/local/riscv64/objs/argon2/src/argon2.o.d @@ -0,0 +1,7 @@ +./obj/local/riscv64/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: diff --git a/obj/local/riscv64/objs/argon2/src/blake2b.o b/obj/local/riscv64/objs/argon2/src/blake2b.o new file mode 100644 index 0000000..01fc560 Binary files /dev/null and b/obj/local/riscv64/objs/argon2/src/blake2b.o differ diff --git a/obj/local/riscv64/objs/argon2/src/blake2b.o.d b/obj/local/riscv64/objs/argon2/src/blake2b.o.d new file mode 100644 index 0000000..fd138cf --- /dev/null +++ b/obj/local/riscv64/objs/argon2/src/blake2b.o.d @@ -0,0 +1,7 @@ +./obj/local/riscv64/objs/argon2/src/blake2b.o: \ + android/app/src/main/jni/src/blake2b.c \ + android/app/src/main/jni/blake2.h android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/blake2-impl.h +android/app/src/main/jni/blake2.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/blake2-impl.h: diff --git a/obj/local/riscv64/objs/argon2/src/core.o b/obj/local/riscv64/objs/argon2/src/core.o new file mode 100644 index 0000000..f56f78d Binary files /dev/null and b/obj/local/riscv64/objs/argon2/src/core.o differ diff --git a/obj/local/riscv64/objs/argon2/src/core.o.d b/obj/local/riscv64/objs/argon2/src/core.o.d new file mode 100644 index 0000000..21736f9 --- /dev/null +++ b/obj/local/riscv64/objs/argon2/src/core.o.d @@ -0,0 +1,10 @@ +./obj/local/riscv64/objs/argon2/src/core.o: \ + android/app/src/main/jni/src/core.c android/app/src/main/jni/core.h \ + android/app/src/main/jni/argon2.h android/app/src/main/jni/thread.h \ + android/app/src/main/jni/src/blake2/blake2.h \ + android/app/src/main/jni/src/blake2/blake2-impl.h +android/app/src/main/jni/core.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/thread.h: +android/app/src/main/jni/src/blake2/blake2.h: +android/app/src/main/jni/src/blake2/blake2-impl.h: diff --git a/obj/local/x86/libargon2.so b/obj/local/x86/libargon2.so new file mode 100644 index 0000000..9921cb7 Binary files /dev/null and b/obj/local/x86/libargon2.so differ diff --git a/obj/local/x86/objs/argon2/src/argon2.o b/obj/local/x86/objs/argon2/src/argon2.o new file mode 100644 index 0000000..4edcd67 Binary files /dev/null and b/obj/local/x86/objs/argon2/src/argon2.o differ diff --git a/obj/local/x86/objs/argon2/src/argon2.o.d b/obj/local/x86/objs/argon2/src/argon2.o.d new file mode 100644 index 0000000..c4bb0d2 --- /dev/null +++ b/obj/local/x86/objs/argon2/src/argon2.o.d @@ -0,0 +1,7 @@ +./obj/local/x86/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: diff --git a/obj/local/x86/objs/argon2/src/blake2b.o b/obj/local/x86/objs/argon2/src/blake2b.o new file mode 100644 index 0000000..f3c212a Binary files /dev/null and b/obj/local/x86/objs/argon2/src/blake2b.o differ diff --git a/obj/local/x86/objs/argon2/src/blake2b.o.d b/obj/local/x86/objs/argon2/src/blake2b.o.d new file mode 100644 index 0000000..f8c787a --- /dev/null +++ b/obj/local/x86/objs/argon2/src/blake2b.o.d @@ -0,0 +1,7 @@ +./obj/local/x86/objs/argon2/src/blake2b.o: \ + android/app/src/main/jni/src/blake2b.c \ + android/app/src/main/jni/blake2.h android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/blake2-impl.h +android/app/src/main/jni/blake2.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/blake2-impl.h: diff --git a/obj/local/x86/objs/argon2/src/core.o b/obj/local/x86/objs/argon2/src/core.o new file mode 100644 index 0000000..c5b387e Binary files /dev/null and b/obj/local/x86/objs/argon2/src/core.o differ diff --git a/obj/local/x86/objs/argon2/src/core.o.d b/obj/local/x86/objs/argon2/src/core.o.d new file mode 100644 index 0000000..f31078f --- /dev/null +++ b/obj/local/x86/objs/argon2/src/core.o.d @@ -0,0 +1,10 @@ +./obj/local/x86/objs/argon2/src/core.o: \ + android/app/src/main/jni/src/core.c android/app/src/main/jni/core.h \ + android/app/src/main/jni/argon2.h android/app/src/main/jni/thread.h \ + android/app/src/main/jni/src/blake2/blake2.h \ + android/app/src/main/jni/src/blake2/blake2-impl.h +android/app/src/main/jni/core.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/thread.h: +android/app/src/main/jni/src/blake2/blake2.h: +android/app/src/main/jni/src/blake2/blake2-impl.h: diff --git a/obj/local/x86/objs/argon2/src/encoding.o b/obj/local/x86/objs/argon2/src/encoding.o new file mode 100644 index 0000000..acd2edd Binary files /dev/null and b/obj/local/x86/objs/argon2/src/encoding.o differ diff --git a/obj/local/x86/objs/argon2/src/encoding.o.d b/obj/local/x86/objs/argon2/src/encoding.o.d new file mode 100644 index 0000000..b5ad9b1 --- /dev/null +++ b/obj/local/x86/objs/argon2/src/encoding.o.d @@ -0,0 +1,7 @@ +./obj/local/x86/objs/argon2/src/encoding.o: \ + android/app/src/main/jni/src/encoding.c \ + android/app/src/main/jni/encoding.h android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/core.h +android/app/src/main/jni/encoding.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/core.h: diff --git a/obj/local/x86/objs/argon2/src/ref.o b/obj/local/x86/objs/argon2/src/ref.o new file mode 100644 index 0000000..dde8dde Binary files /dev/null and b/obj/local/x86/objs/argon2/src/ref.o differ diff --git a/obj/local/x86/objs/argon2/src/ref.o.d b/obj/local/x86/objs/argon2/src/ref.o.d new file mode 100644 index 0000000..ca9ed55 --- /dev/null +++ b/obj/local/x86/objs/argon2/src/ref.o.d @@ -0,0 +1,10 @@ +./obj/local/x86/objs/argon2/src/ref.o: android/app/src/main/jni/src/ref.c \ + android/app/src/main/jni/argon2.h android/app/src/main/jni/core.h \ + android/app/src/main/jni/src/blake2/blamka-round-ref.h \ + android/app/src/main/jni/src/blake2/blake2.h \ + android/app/src/main/jni/src/blake2/blake2-impl.h +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/core.h: +android/app/src/main/jni/src/blake2/blamka-round-ref.h: +android/app/src/main/jni/src/blake2/blake2.h: +android/app/src/main/jni/src/blake2/blake2-impl.h: diff --git a/obj/local/x86/objs/argon2/src/thread.o b/obj/local/x86/objs/argon2/src/thread.o new file mode 100644 index 0000000..0af4340 Binary files /dev/null and b/obj/local/x86/objs/argon2/src/thread.o differ diff --git a/obj/local/x86/objs/argon2/src/thread.o.d b/obj/local/x86/objs/argon2/src/thread.o.d new file mode 100644 index 0000000..36e00d9 --- /dev/null +++ b/obj/local/x86/objs/argon2/src/thread.o.d @@ -0,0 +1,4 @@ +./obj/local/x86/objs/argon2/src/thread.o: \ + android/app/src/main/jni/src/thread.c \ + android/app/src/main/jni/thread.h +android/app/src/main/jni/thread.h: diff --git a/obj/local/x86_64/libargon2.so b/obj/local/x86_64/libargon2.so new file mode 100644 index 0000000..5413a6b Binary files /dev/null and b/obj/local/x86_64/libargon2.so differ diff --git a/obj/local/x86_64/objs/argon2/src/argon2.o b/obj/local/x86_64/objs/argon2/src/argon2.o new file mode 100644 index 0000000..1fe8042 Binary files /dev/null and b/obj/local/x86_64/objs/argon2/src/argon2.o differ diff --git a/obj/local/x86_64/objs/argon2/src/argon2.o.d b/obj/local/x86_64/objs/argon2/src/argon2.o.d new file mode 100644 index 0000000..3388fc4 --- /dev/null +++ b/obj/local/x86_64/objs/argon2/src/argon2.o.d @@ -0,0 +1,7 @@ +./obj/local/x86_64/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: diff --git a/obj/local/x86_64/objs/argon2/src/blake2b.o b/obj/local/x86_64/objs/argon2/src/blake2b.o new file mode 100644 index 0000000..f89d50a Binary files /dev/null and b/obj/local/x86_64/objs/argon2/src/blake2b.o differ diff --git a/obj/local/x86_64/objs/argon2/src/blake2b.o.d b/obj/local/x86_64/objs/argon2/src/blake2b.o.d new file mode 100644 index 0000000..306443a --- /dev/null +++ b/obj/local/x86_64/objs/argon2/src/blake2b.o.d @@ -0,0 +1,7 @@ +./obj/local/x86_64/objs/argon2/src/blake2b.o: \ + android/app/src/main/jni/src/blake2b.c \ + android/app/src/main/jni/blake2.h android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/blake2-impl.h +android/app/src/main/jni/blake2.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/blake2-impl.h: diff --git a/obj/local/x86_64/objs/argon2/src/core.o b/obj/local/x86_64/objs/argon2/src/core.o new file mode 100644 index 0000000..31aa4c9 Binary files /dev/null and b/obj/local/x86_64/objs/argon2/src/core.o differ diff --git a/obj/local/x86_64/objs/argon2/src/core.o.d b/obj/local/x86_64/objs/argon2/src/core.o.d new file mode 100644 index 0000000..699f096 --- /dev/null +++ b/obj/local/x86_64/objs/argon2/src/core.o.d @@ -0,0 +1,10 @@ +./obj/local/x86_64/objs/argon2/src/core.o: \ + android/app/src/main/jni/src/core.c android/app/src/main/jni/core.h \ + android/app/src/main/jni/argon2.h android/app/src/main/jni/thread.h \ + android/app/src/main/jni/src/blake2/blake2.h \ + android/app/src/main/jni/src/blake2/blake2-impl.h +android/app/src/main/jni/core.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/thread.h: +android/app/src/main/jni/src/blake2/blake2.h: +android/app/src/main/jni/src/blake2/blake2-impl.h: diff --git a/obj/local/x86_64/objs/argon2/src/encoding.o b/obj/local/x86_64/objs/argon2/src/encoding.o new file mode 100644 index 0000000..39c3c1b Binary files /dev/null and b/obj/local/x86_64/objs/argon2/src/encoding.o differ diff --git a/obj/local/x86_64/objs/argon2/src/encoding.o.d b/obj/local/x86_64/objs/argon2/src/encoding.o.d new file mode 100644 index 0000000..e1279ed --- /dev/null +++ b/obj/local/x86_64/objs/argon2/src/encoding.o.d @@ -0,0 +1,7 @@ +./obj/local/x86_64/objs/argon2/src/encoding.o: \ + android/app/src/main/jni/src/encoding.c \ + android/app/src/main/jni/encoding.h android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/core.h +android/app/src/main/jni/encoding.h: +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/core.h: diff --git a/obj/local/x86_64/objs/argon2/src/ref.o b/obj/local/x86_64/objs/argon2/src/ref.o new file mode 100644 index 0000000..1bc4f48 Binary files /dev/null and b/obj/local/x86_64/objs/argon2/src/ref.o differ diff --git a/obj/local/x86_64/objs/argon2/src/ref.o.d b/obj/local/x86_64/objs/argon2/src/ref.o.d new file mode 100644 index 0000000..ff1788e --- /dev/null +++ b/obj/local/x86_64/objs/argon2/src/ref.o.d @@ -0,0 +1,11 @@ +./obj/local/x86_64/objs/argon2/src/ref.o: \ + android/app/src/main/jni/src/ref.c android/app/src/main/jni/argon2.h \ + android/app/src/main/jni/core.h \ + android/app/src/main/jni/src/blake2/blamka-round-ref.h \ + android/app/src/main/jni/src/blake2/blake2.h \ + android/app/src/main/jni/src/blake2/blake2-impl.h +android/app/src/main/jni/argon2.h: +android/app/src/main/jni/core.h: +android/app/src/main/jni/src/blake2/blamka-round-ref.h: +android/app/src/main/jni/src/blake2/blake2.h: +android/app/src/main/jni/src/blake2/blake2-impl.h: diff --git a/obj/local/x86_64/objs/argon2/src/thread.o b/obj/local/x86_64/objs/argon2/src/thread.o new file mode 100644 index 0000000..91ed3bd Binary files /dev/null and b/obj/local/x86_64/objs/argon2/src/thread.o differ diff --git a/obj/local/x86_64/objs/argon2/src/thread.o.d b/obj/local/x86_64/objs/argon2/src/thread.o.d new file mode 100644 index 0000000..d17c4fe --- /dev/null +++ b/obj/local/x86_64/objs/argon2/src/thread.o.d @@ -0,0 +1,4 @@ +./obj/local/x86_64/objs/argon2/src/thread.o: \ + android/app/src/main/jni/src/thread.c \ + android/app/src/main/jni/thread.h +android/app/src/main/jni/thread.h: diff --git a/pubspec.lock b/pubspec.lock index 1790079..390b734 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + url: "https://pub.dev" + source: hosted + version: "3.6.1" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: "6b151826fcc95ff246cd219a0bf4c753ea14f4081ad71c61939becf3aba27f70" + url: "https://pub.dev" + source: hosted + version: "1.5.5" async: dependency: transitive description: @@ -9,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bcrypt: + dependency: "direct main" + description: + name: bcrypt + sha256: "9dc3f234d5935a76917a6056613e1a6d9b53f7fa56f98e24cd49b8969307764b" + url: "https://pub.dev" + source: hosted + version: "1.1.3" bloc: dependency: transitive description: @@ -25,6 +57,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + camerawesome: + dependency: "direct main" + description: + name: camerawesome + sha256: "3619d5605fb14ab72c815532c1d9f635512c75df07b5a742b60a9a4b03b6081e" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae" + url: "https://pub.dev" + source: hosted + version: "5.0.0" characters: dependency: transitive description: @@ -49,8 +97,32 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + colorfilter_generator: + dependency: transitive + description: + name: colorfilter_generator + sha256: ccc2995e440b1d828d55d99150e7cad64624f3cb4a1e235000de3f93cf10d35c + url: "https://pub.dev" + source: hosted + version: "0.0.8" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: - dependency: "direct main" + dependency: transitive description: name: crypto sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 @@ -81,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.1" + encrypt: + dependency: "direct main" + description: + name: encrypt + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" + url: "https://pub.dev" + source: hosted + version: "5.0.3" equatable: dependency: "direct main" description: @@ -113,11 +193,59 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + flare_flutter: + dependency: "direct main" + description: + name: flare_flutter + sha256: "99d63c60f00fac81249ce6410ee015d7b125c63d8278a30da81edf3317a1f6a0" + url: "https://pub.dev" + source: hosted + version: "3.0.2" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_bcrypt: + dependency: "direct main" + description: + name: flutter_bcrypt + sha256: "47dd6d864732ec0e19f950ba7e67b5b245bcdbe8bada288689de4b6c405c2d37" + url: "https://pub.dev" + source: hosted + version: "1.0.8" flutter_bloc: dependency: "direct main" description: @@ -190,11 +318,27 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.3" + flutter_spinkit: + dependency: "direct main" + description: + name: flutter_spinkit + sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472 + url: "https://pub.dev" + source: hosted + version: "5.2.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_vibrate: + dependency: "direct main" + description: + name: flutter_vibrate + sha256: "9cc9b32cf52c90dd34c1cf396ed40010b2c74e69adbb0ff16005afa900971ad8" + url: "https://pub.dev" + source: hosted + version: "1.3.0" flutter_web_plugins: dependency: transitive description: flutter @@ -236,10 +380,10 @@ packages: dependency: transitive description: name: google_maps_flutter_android - sha256: "60a005bf1ba8d178144e442f6e2d734b0ffc2cc800a05415388472f934ad6d6a" + sha256: "36e75af1d0bd4c7391eacdaedf9ca7632c5b9709c5ec618b04489b79ea2b3f82" url: "https://pub.dev" source: hosted - version: "2.14.4" + version: "2.14.6" google_maps_flutter_ios: dependency: transitive description: @@ -252,10 +396,10 @@ packages: dependency: transitive description: name: google_maps_flutter_platform_interface - sha256: "4f6930fd668bf5d40feb2695d5695dbc0c35e5542b557a34ad35be491686d2ba" + sha256: "099874463dc4c9bff04fe4b2b8cf7284d2455c2deead8f9a59a87e1b9f028c69" url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.9.2" google_maps_flutter_web: dependency: transitive description: @@ -288,6 +432,78 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c + url: "https://pub.dev" + source: hosted + version: "0.8.9" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85 + url: "https://pub.dev" + source: hosted + version: "0.8.12+13" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + url: "https://pub.dev" + source: hosted + version: "0.8.12" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" intl: dependency: "direct main" description: @@ -336,6 +552,22 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + loading_icon_button: + dependency: "direct main" + description: + name: loading_icon_button + sha256: "5ef8c82796c19b96a5995457410037cbe05cb4840af766e50330b2d108dacdfd" + url: "https://pub.dev" + source: hosted + version: "0.0.6" + logger: + dependency: "direct main" + description: + name: logger + sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f" + url: "https://pub.dev" + source: hosted + version: "1.4.0" matcher: dependency: transitive description: @@ -352,6 +584,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.1" + matrix2d: + dependency: transitive + description: + name: matrix2d + sha256: "188718dd3bc2a31e372cfd0791b0f77f4f13ea76164147342cc378d9132949e7" + url: "https://pub.dev" + source: hosted + version: "1.0.4" meta: dependency: transitive description: @@ -360,6 +600,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + mime: + dependency: transitive + description: + name: mime + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + url: "https://pub.dev" + source: hosted + version: "1.0.6" nested: dependency: transitive description: @@ -376,6 +624,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + url: "https://pub.dev" + source: hosted + version: "2.1.4" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + url: "https://pub.dev" + source: hosted + version: "2.2.10" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -400,6 +672,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5 + url: "https://pub.dev" + source: hosted + version: "10.4.5" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47" + url: "https://pub.dev" + source: hosted + version: "10.3.6" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" + url: "https://pub.dev" + source: hosted + version: "9.1.4" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" + url: "https://pub.dev" + source: hosted + version: "3.12.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 + url: "https://pub.dev" + source: hosted + version: "0.1.3" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" platform: dependency: transitive description: @@ -416,14 +736,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - provider: + pointycastle: dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + provider: + dependency: "direct main" description: name: provider sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c url: "https://pub.dev" source: hosted version: "6.1.2" + rxdart: + dependency: "direct overridden" + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" sanitize_html: dependency: transitive description: @@ -565,6 +901,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d + url: "https://pub.dev" + source: hosted + version: "2.9.1" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "38d8fe136c427abdce68b5e8c3c08ea29d7a794b453c7a51b12ecfad4aad9437" + url: "https://pub.dev" + source: hosted + version: "2.7.3" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c + url: "https://pub.dev" + source: hosted + version: "2.6.1" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" + url: "https://pub.dev" + source: hosted + version: "6.2.2" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "6dcdd298136523eaf7dfc31abaf0dfba9aa8a8dbc96670e87e9d42b6f2caf774" + url: "https://pub.dev" + source: hosted + version: "2.3.2" vm_service: dependency: transitive description: @@ -589,6 +965,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" sdks: dart: ">=3.5.1 <4.0.0" - flutter: ">=3.22.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 94a73d3..76fd946 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,15 +10,30 @@ environment: dependencies: flutter: sdk: flutter + + image_picker: ^0.8.4+8 + camerawesome: ^2.1.0 + path_provider: ^2.0.11 + permission_handler: ^10.2.0 + video_player: ^2.2.19 + carousel_slider: ^5.0.0 + bcrypt: ^1.1.3 + flutter_bcrypt: ^1.0.8 + encrypt: ^5.0.0 + provider: ^6.0.0 + flare_flutter: ^3.0.0 + logger: ^1.1.0 + flutter_spinkit: ^5.1.0 + flutter_vibrate: ^1.3.0 + loading_icon_button: ^0.0.6 intl: ^0.18.0 fluttertoast: ^8.0.8 flutter_secure_storage: ^7.0.1 - crypto: ^3.0.1 - google_maps_flutter: ^2.0.10 + google_maps_flutter: ^2.9.0 cupertino_icons: ^1.0.8 # State management with flutter_bloc - flutter_bloc: ^8.0.1 + flutter_bloc: ^8.0.9 # HTTP client for API requests http: ^0.13.3 @@ -31,7 +46,10 @@ dependencies: # Functional programming utilities dartz: ^0.10.1 - + +dependency_overrides: + rxdart: ^0.28.0 + dev_dependencies: flutter_test: sdk: flutter @@ -44,10 +62,28 @@ flutter: assets: - lib/assets/images/logo.png + - lib/assets/images/logolionsdev.png + - lib/assets/images/logoaw.png - lib/assets/images/background.webp - lib/assets/images/profile_picture.png - lib/assets/images/placeholder.png - lib/assets/json/event_categories.json + - lib/assets/images/friend_placeholder.png + - lib/assets/images/group_placeholder.png + - lib/assets/images/event_placeholder.png + - lib/assets/images/activity_placeholder.png + - lib/assets/images/story_placeholder.png + - lib/assets/images/user_placeholder.png + - lib/assets/videos/test.mp4 + + fonts: + - family: Montserrat + fonts: + - asset: lib/assets/fonts/montserrat_regular.ttf + - asset: lib/assets/fonts/montserrat_bold.ttf + weight: 700 + + # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 0c50753..a039629 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4fc759c..bade488 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,7 +3,9 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows flutter_secure_storage_windows + permission_handler_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST