diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/data/datasources/remote_remote_data_source.dart b/aait/mobile/group-1/blog_app/lib/features/blog/data/datasources/remote_remote_data_source.dart new file mode 100644 index 000000000..0614557ea --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/data/datasources/remote_remote_data_source.dart @@ -0,0 +1,41 @@ +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:http/http.dart' as http; +import '../../../../core/utils/constants.dart'; +import '../models/article_model.dart'; +import '../models/create_article_model.dart'; + +abstract class ArticleRemoteDataSource { + Future postArticle(CreateArticleModel articleModel); + Future updateArticle(CreateArticleModel articleModel); +} + +class ArticleRemoteDataSourceImpl extends ArticleRemoteDataSource { + @override + Future postArticle(CreateArticleModel articleModel) async { + final String? token = await getToken(); + final response = await http.post(Uri.parse('$baseApi/article'), + headers: {'Content-Type': 'application/json', "token": token!}, + body: json.encode(articleModel.toJson())); + + return ArticleModel.fromJson(jsonDecode(response.body)); + } + + @override + Future updateArticle(CreateArticleModel articleModel) async { + final String? token = await getToken(); + final id = articleModel.id; + final response = await http.post( + Uri.parse('$baseApi/article/$id'), + headers: {'Content-Type': 'application/json', "token": token!}, + body: json.encode(articleModel.toJson())); + + return ArticleModel.fromJson(jsonDecode(response.body)); + } + + Future getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('token'); + } +} diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/data/models/article_model.dart b/aait/mobile/group-1/blog_app/lib/features/blog/data/models/article_model.dart new file mode 100644 index 000000000..49cffd156 --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/data/models/article_model.dart @@ -0,0 +1,81 @@ +import 'dart:convert'; + +import 'package:equatable/equatable.dart'; + +import '../../domain/entities/article_enitity.dart'; + +class ArticleModel extends Article implements Equatable { + ArticleModel( + {required this.id, + required this.title, + required this.subTitle, + required this.tags, + this.user, + required this.content, + this.image, + this.estimatedtime, + this.imageCloudinaryPublicId, + this.createdAt}) + : super( + id: id, + title: title, + subTitle: subTitle, + user: user, + tags: tags, + content: content, + image: image, + estimatedtime: estimatedtime, + imageCloudinaryPublicId: imageCloudinaryPublicId, + createdAt: createdAt, + ); + + @override + final String id; + @override + final String title; + @override + final String subTitle; + @override + final String? user; + @override + final List tags; + @override + final String content; + @override + final String? image; + @override + final String? estimatedtime; + @override + final String? imageCloudinaryPublicId; + @override + final DateTime? createdAt; + + factory ArticleModel.fromJson(Map json) { + return ArticleModel( + id: json['id'], + title: json["title"], + subTitle: json['subTitile'], + content: json['content'], + tags: jsonDecode(json['tags']), + user: json['user'], + image: json['image'], + estimatedtime: json['estimatedReadTime'], + imageCloudinaryPublicId: json['imageCloudinaryPublicId'], + createdAt: json['createdAt']); + } + + Map toJson(ArticleModel articleModel) { + return { + 'title': articleModel.title, + 'subTitle': articleModel.subTitle, + 'content': articleModel.content, + 'tags': jsonEncode(articleModel.tags) + }; + } + + @override + List get props => [id]; + + @override + bool? get stringify => false; +} diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/data/models/create_article_model.dart b/aait/mobile/group-1/blog_app/lib/features/blog/data/models/create_article_model.dart new file mode 100644 index 000000000..b2909459a --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/data/models/create_article_model.dart @@ -0,0 +1,64 @@ +import 'dart:convert'; + +import 'package:equatable/equatable.dart'; + +import '../../domain/entities/create_article_entity.dart'; + +class CreateArticleModel extends CreateArticleEntity implements Equatable { + CreateArticleModel({ + required this.title, + required this.subTitle, + required this.tags, + required this.content, + this.id, + this.image, + this.estimatedtime, + }) : super( + title: title, + subTitle: subTitle, + tags: tags, + content: content, + image: image, + estimatedtime: estimatedtime, + ); + + @override + final String title; + @override + final String subTitle; + @override + final String? id; + @override + final List tags; + @override + final String content; + @override + final String? image; + @override + final String? estimatedtime; + + factory CreateArticleModel.fromJson(Map json) { + return CreateArticleModel( + title: json["title"], + subTitle: json['subTitile'], + content: json['content'], + tags: jsonDecode(json['tags']), + image: json['image'], + estimatedtime: json['estimatedReadTime']); + } + + Map toJson() { + return { + 'title': title, + 'subTitle': subTitle, + 'content': content, + 'tags': tags + }; + } + + @override + List get props => []; + + @override + bool? get stringify => false; +} diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/data/repository/article_repository_implimentation.dart b/aait/mobile/group-1/blog_app/lib/features/blog/data/repository/article_repository_implimentation.dart new file mode 100644 index 000000000..c26aaed59 --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/data/repository/article_repository_implimentation.dart @@ -0,0 +1,52 @@ +import 'package:blog_app/core/errors/failures/failure.dart'; +import 'package:blog_app/core/network/network_info.dart'; +import 'package:blog_app/features/blog/data/datasources/remote_remote_data_source.dart'; +import 'package:blog_app/features/blog/data/models/create_article_model.dart'; +import 'package:blog_app/features/blog/domain/entities/article_enitity.dart'; +import 'package:blog_app/features/blog/domain/repositories/article_repository.dart'; +import 'package:dartz/dartz.dart'; + +import '../../domain/entities/create_article_entity.dart'; + +class ArticleRepositoryImpl extends ArticleRepository { + final NetworkInfo networkInfo; + final ArticleRemoteDataSource remoteDataSource; + + ArticleRepositoryImpl( + {required this.networkInfo, required this.remoteDataSource}); + + @override + Future> createArticle( + CreateArticleEntity article) async { + CreateArticleModel createArticleModel = CreateArticleModel( + title: article.title, + subTitle: article.subTitle, + tags: article.tags, + content: article.content); + final isConnected = await networkInfo.isConnected; + if (isConnected) { + final article = await remoteDataSource.postArticle(createArticleModel); + return Right(article); + } else { + return Left(ConnectionFailure(message: "Failed to connect to the ethernet")); + } + } + + @override + Future> updateArticle( + CreateArticleEntity article) async { + CreateArticleModel createArticleModel = CreateArticleModel( + title: article.title, + subTitle: article.subTitle, + tags: article.tags, + content: article.content); + final isConnected = await networkInfo.isConnected; + if (isConnected) { + final article = await remoteDataSource.updateArticle(createArticleModel); + return Right(article); + } else { + return Left( + ConnectionFailure(message: "Failed to connect to the ethernet")); + } + } +} diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/article_enitity.dart b/aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/article_enitity.dart new file mode 100644 index 000000000..3877ff705 --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/article_enitity.dart @@ -0,0 +1,24 @@ +class Article { + final String id; + final String title; + final String subTitle; + final String? user; + final List tags; + final String content; + final String? image; + final String? estimatedtime; + final String? imageCloudinaryPublicId; + final DateTime? createdAt; + + Article( + {required this.id, + required this.title, + required this.subTitle, + required this.tags, + this.user, + required this.content, + this.image, + this.estimatedtime, + this.imageCloudinaryPublicId, + this.createdAt}); +} diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/create_article_entity.dart b/aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/create_article_entity.dart new file mode 100644 index 000000000..8e63111cd --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/create_article_entity.dart @@ -0,0 +1,18 @@ +class CreateArticleEntity { + final String? id; + final String title; + final String subTitle; + final List tags; + final String content; + final String? image; + final String? estimatedtime; + + CreateArticleEntity( + {this.id, + required this.title, + required this.subTitle, + required this.tags, + required this.content, + this.image, + this.estimatedtime}); +} diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/domain/repositories/article_repository.dart b/aait/mobile/group-1/blog_app/lib/features/blog/domain/repositories/article_repository.dart new file mode 100644 index 000000000..5b4bcfa2f --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/domain/repositories/article_repository.dart @@ -0,0 +1,10 @@ +import 'package:dartz/dartz.dart'; + +import '../../../../core/errors/failures/failure.dart'; +import '../entities/article_enitity.dart'; +import '../entities/create_article_entity.dart'; + +abstract class ArticleRepository { + Future> createArticle(CreateArticleEntity article); + Future> updateArticle(CreateArticleEntity article); +} diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/create_article.dart b/aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/create_article.dart new file mode 100644 index 000000000..decf645ea --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/create_article.dart @@ -0,0 +1,16 @@ +import 'package:dartz/dartz.dart'; + +import '../../../../core/errors/failures/failure.dart'; +import '../entities/article_enitity.dart'; +import '../entities/create_article_entity.dart'; +import '../repositories/article_repository.dart'; + +class CreateArticle { + final ArticleRepository repository; + CreateArticle(this.repository); + + Future> call(CreateArticleEntity article) async { + return await repository.createArticle(article); + } +} + diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/update_article.dart b/aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/update_article.dart new file mode 100644 index 000000000..b21d46975 --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/update_article.dart @@ -0,0 +1,16 @@ +import 'package:blog_app/core/errors/failures/failure.dart'; +import 'package:blog_app/features/blog/domain/entities/article_enitity.dart'; +import 'package:blog_app/features/blog/domain/repositories/article_repository.dart'; +import 'package:dartz/dartz.dart'; + +import '../entities/create_article_entity.dart'; + +class UpdateArticle { + final ArticleRepository repository; + UpdateArticle(this.repository); + + Future> call(CreateArticleEntity article) async { + return await repository.updateArticle(article); + } +} + diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/presentation/pages/create_blog.dart b/aait/mobile/group-1/blog_app/lib/features/blog/presentation/pages/create_blog.dart new file mode 100644 index 000000000..91cc5914e --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/presentation/pages/create_blog.dart @@ -0,0 +1,254 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../user_profile/presentation/bloc/profile_bloc.dart'; + +class CreateBlogPage extends StatefulWidget { + const CreateBlogPage({super.key}); + + @override + State createState() => _CreateBlogPageState(); +} + +class _CreateBlogPageState extends State { + @override + void initState() { + BlocProvider.of(context).add(GetProfileInfo()); + super.initState(); + } + List tags = [ + "Sports", + "Tech", + "Politics", + "Art", + "Design", + "Culture", + "Production", + "Others", + ]; + // List selected = List.generate(tags.length, (index) => false); + Map tagsMap = { + "Sports": false, + "Tech": false, + "Politics": false, + "Art": false, + "Design": false, + "Culture": false, + "Production": false, + "Others": false, + }; + + @override + Widget build(BuildContext context) { + + + bool tonalSelected = true; + + final formKey = GlobalKey(); + + final titleController = TextEditingController(); + final subTitleController = TextEditingController(); + final articleContent = TextEditingController(); + return Scaffold( + body: BlocBuilder( + builder: (context, state) { + if ((state is! Loaded)) { + return SafeArea( + child: Container( + padding: const EdgeInsets.all(25), + height: MediaQuery.of(context).size.height, + child: ListView( + children: [ + Row( + children: [ + IconButton.filledTonal( + isSelected: tonalSelected, + icon: const Icon(Icons + .arrow_back_ios_new_rounded), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.1, + ), + const Text( + "New Article", + style: TextStyle( + fontSize: 23, fontWeight: FontWeight.w600), + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 7, horizontal: 15), + child: TextFormField( + controller: titleController, + keyboardType: TextInputType.name, + decoration: const InputDecoration( + hintText: "Add title", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 20.0, + fontWeight: FontWeight.w300), + ), + style: const TextStyle( + color: Colors.black, + fontSize: 22.0, + fontWeight: FontWeight.w500), + validator: (String? name) { + if (name == null || name.isEmpty) { + return "Name can not be empty"; + } + return null; + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 7, horizontal: 15), + child: TextFormField( + controller: subTitleController, + keyboardType: TextInputType.name, + decoration: const InputDecoration( + hintText: "Add subtitle", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 20.0, + fontWeight: FontWeight.w300), + ), + style: const TextStyle( + + color: Colors.black, + fontSize: 21.0, + fontWeight: + FontWeight.w400 + ), + validator: (String? name) { + if (name == null || name.isEmpty) { + return "Name can not be empty"; + } + return null; + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 7, horizontal: 15), + child: Wrap( + spacing: 8.0, + runSpacing: 4.0, + children: List.generate(tags.length, (index) { + return ChoiceChip( + label: Text(tags[index]), + selected: tagsMap[tags[index]]!, + onSelected: (isSelected) { + setState(() { + tagsMap[tags[index]] = isSelected; + }); + }, + elevation: 0, + backgroundColor: tagsMap[tags[index]]! + ? const Color(0xFF376AED) + : Colors.grey[300], + selectedColor: const Color(0xFF376AED), + labelStyle: TextStyle( + color: tagsMap[tags[index]]! + ? Colors.white + : Colors.black, + ), + ); + })), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 7, horizontal: 15), + child: TextFormField( + controller: articleContent, + maxLines: 11, + keyboardType: TextInputType.name, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(10.0)), + borderSide: + BorderSide(color: Colors.grey, width: 0)), + hintText: "article content", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 17.0, + fontWeight: FontWeight.w300), + ), + style: const TextStyle( + // Style for input text + color: Colors.black, // Color of input text + fontSize: 19.0, + fontWeight: + FontWeight.w400 // Font size of input text + ), + validator: (String? name) { + if (name == null || name.isEmpty) { + return "Name can not be empty"; + } + return null; + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(), + ElevatedButton( + onPressed: () async { + final formValid = + formKey.currentState!.validate(); + if (!formValid) { + return; + } + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + const Color(0xFF376AED)), + shape: MaterialStateProperty.all< + RoundedRectangleBorder>( + const RoundedRectangleBorder( + borderRadius: + BorderRadius.all(Radius.circular(50)), + ), + ), + ), + child: const Padding( + padding: EdgeInsets.symmetric( + vertical: 8.0, horizontal: 15.0), + child: Text( + style: TextStyle( + fontSize: 17, color: Colors.white), + "Publish", + ), + ), + ), + const SizedBox() + ], + ), + ) + ], + ), + ), + ); + } else if (state is Error) { + return const Center( + child: Text("Error"), + ); + } else { + return const Center( + child: Text("Loading"), + ); + } + }, + ), + ); + } +} diff --git a/aait/mobile/group-1/blog_app/lib/features/user_profile/data/repository/user_repository_implementaion.dart b/aait/mobile/group-1/blog_app/lib/features/user_profile/data/repository/user_repository_implementaion.dart index e7147957c..c5200e72a 100644 --- a/aait/mobile/group-1/blog_app/lib/features/user_profile/data/repository/user_repository_implementaion.dart +++ b/aait/mobile/group-1/blog_app/lib/features/user_profile/data/repository/user_repository_implementaion.dart @@ -1,5 +1,4 @@ import 'package:blog_app/core/errors/failures/failure.dart'; -import 'package:blog_app/core/errors/failures/server_failure.dart'; import 'package:blog_app/features/user_profile/data/datasources/profile_local_data_source.dart'; import 'package:blog_app/features/user_profile/data/datasources/proile_remote_data_source.dart'; import 'package:blog_app/features/user_profile/domain/entities/user_entity.dart'; @@ -27,7 +26,7 @@ class UserRepositoryImpl extends UserRepository { return Right(user); } catch (e) { print("error occured $e"); - return Left(ServerFailure(e.toString())); + return Left(ServerFailure(message: e.toString())); } } else { try { @@ -35,7 +34,7 @@ class UserRepositoryImpl extends UserRepository { return Right(user); } catch (e) { print("error occured $e"); - return Left(ServerFailure(e.toString())); + return Left(ServerFailure(message: e.toString())); } } } @@ -47,7 +46,7 @@ class UserRepositoryImpl extends UserRepository { return Right(user); } catch (e) { print("error occured $e"); - return Left(ServerFailure(e.toString())); + return Left(ServerFailure(message: e.toString())); } } }