From 136ff38532649957dfa28ea40512bbe17d31a71c Mon Sep 17 00:00:00 2001 From: fikremariamF <91600690+fikremariamF@users.noreply.github.com> Date: Fri, 25 Aug 2023 11:34:34 +0300 Subject: [PATCH 1/6] Aritcle page presentation, domain and data layer implimentations which does not include bloc --- .../remote_remote_data_source.dart | 41 +++ .../blog/data/models/article_model.dart | 81 ++++++ .../data/models/create_article_model.dart | 64 +++++ .../article_repository_implimentation.dart | 52 ++++ .../blog/domain/entities/article_enitity.dart | 24 ++ .../entities/create_article_entity.dart | 18 ++ .../repositories/article_repository.dart | 10 + .../blog/domain/usecases/create_article.dart | 16 ++ .../blog/domain/usecases/update_article.dart | 16 ++ .../blog/presentation/pages/create_blog.dart | 254 ++++++++++++++++++ .../user_repository_implementaion.dart | 7 +- 11 files changed, 579 insertions(+), 4 deletions(-) create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/data/datasources/remote_remote_data_source.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/data/models/article_model.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/data/models/create_article_model.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/data/repository/article_repository_implimentation.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/article_enitity.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/create_article_entity.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/domain/repositories/article_repository.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/create_article.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/update_article.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/presentation/pages/create_blog.dart 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())); } } } From 62c4d594c0c837e8eec20d21bb5a8f149da32a3b Mon Sep 17 00:00:00 2001 From: fikremariamF <91600690+fikremariamF@users.noreply.github.com> Date: Mon, 28 Aug 2023 16:33:09 +0300 Subject: [PATCH 2/6] feat(AAiT-mobile-1): Article --- .../blog/domain/usecases/create_article.dart | 2 +- .../blog/domain/usecases/update_article.dart | 2 +- .../bloc/article_bloc/article_bloc.dart | 32 +++++++++++++++++++ .../bloc/article_bloc/article_event.dart | 7 ++++ .../bloc/article_bloc/article_state.dart | 10 ++++++ .../blog_app/lib/injection_container.dart | 11 +++++++ 6 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_bloc.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_event.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_state.dart 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 index decf645ea..d4c5a33d3 100644 --- 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 @@ -9,7 +9,7 @@ class CreateArticle { final ArticleRepository repository; CreateArticle(this.repository); - Future> call(CreateArticleEntity article) async { + Future> createArticle(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 index b21d46975..e1d995c3d 100644 --- 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 @@ -9,7 +9,7 @@ class UpdateArticle { final ArticleRepository repository; UpdateArticle(this.repository); - Future> call(CreateArticleEntity article) async { + Future> updateArticle(CreateArticleEntity article) async { return await repository.updateArticle(article); } } diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_bloc.dart b/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_bloc.dart new file mode 100644 index 000000000..6c5895c2f --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_bloc.dart @@ -0,0 +1,32 @@ +import 'package:blog_app/features/blog/domain/usecases/create_article.dart'; +import 'package:blog_app/features/blog/domain/usecases/update_article.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../core/network/network_info.dart'; +import '../../../../../injection_container.dart'; +import '../../../data/datasources/remote_remote_data_source.dart'; +import '../../../data/repository/article_repository_implimentation.dart'; +import '../../../domain/repositories/article_repository.dart'; +import 'article_event.dart'; +import 'article_state.dart'; + +class ArticleBloc extends Bloc { + final NetworkInfo networkInfo = sl(); + final ArticleRemoteDataSource remoteDataSource = sl(); + + ArticleBloc() : super(Idle()) { + ArticleRepository repository = ArticleRepositoryImpl( + networkInfo: networkInfo, remoteDataSource: remoteDataSource); + + on((CreateArticleEvent event, Emitter emit) async { + CreateArticle createArticle = CreateArticle(repository); + + }); + + + on((UpdateArticleEvent event, Emitter emit) async { + UpdateArticle updateArticle = UpdateArticle(repository); + + }); + } +} diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_event.dart b/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_event.dart new file mode 100644 index 000000000..1e6e920bb --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_event.dart @@ -0,0 +1,7 @@ + + +abstract class ArticleEvent {} + +class CreateArticleEvent extends ArticleEvent {} + +class UpdateArticleEvent extends ArticleEvent {} diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_state.dart b/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_state.dart new file mode 100644 index 000000000..4d456d39c --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_state.dart @@ -0,0 +1,10 @@ + +abstract class ArticleState {} + +class Idle extends ArticleState {} + +class Error extends ArticleState {} + +class Loading extends ArticleState {} + + diff --git a/aait/mobile/group-1/blog_app/lib/injection_container.dart b/aait/mobile/group-1/blog_app/lib/injection_container.dart index 10fdab057..dbf832420 100644 --- a/aait/mobile/group-1/blog_app/lib/injection_container.dart +++ b/aait/mobile/group-1/blog_app/lib/injection_container.dart @@ -1,3 +1,6 @@ +import 'package:blog_app/features/blog/domain/usecases/create_article.dart'; +import 'package:blog_app/features/blog/domain/usecases/update_article.dart'; +import 'package:blog_app/features/blog/presentation/bloc/article_bloc/article_bloc.dart'; import 'package:blog_app/features/user_profile/data/repository/user_repository_implementaion.dart'; import 'package:blog_app/features/user_profile/domain/repositories/user_repository.dart'; import 'package:blog_app/features/user_profile/domain/usecases/get_user_info.dart'; @@ -8,6 +11,7 @@ import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'core/network/network_info.dart'; +import 'features/blog/data/datasources/remote_remote_data_source.dart'; import 'features/user_profile/data/datasources/profile_local_data_source.dart'; import 'features/user_profile/data/datasources/proile_remote_data_source.dart'; import 'features/user_profile/domain/entities/user_entity.dart'; @@ -18,10 +22,13 @@ Future init() async { //! Features - Task Management // Bloc sl.registerFactory(() => ProfileBloc()); + sl.registerFactory(() => ArticleBloc()); // Use cases sl.registerLazySingleton(() => GetUserInfo(sl())); sl.registerLazySingleton(() => UpdateUserInfo(sl())); + sl.registerLazySingleton(() => UpdateArticle(sl())); + sl.registerLazySingleton(() => CreateArticle(sl())); // Repository sl.registerLazySingleton(() => UserRepositoryImpl( @@ -34,6 +41,8 @@ Future init() async { sl.registerLazySingleton( () => ProfileRemoteDataSourceImpl()); + sl.registerLazySingleton( + () => articleRemoteDataSourceImpl()); // classes sl.registerLazySingleton(() => []); @@ -46,3 +55,5 @@ Future init() async { final sharedPreferences = await SharedPreferences.getInstance(); sl.registerLazySingleton(() => sharedPreferences); } + +articleRemoteDataSourceImpl() {} From 35d66c03311224692b5080334526613dabcaf8b1 Mon Sep 17 00:00:00 2001 From: fikremariamF <91600690+fikremariamF@users.noreply.github.com> Date: Fri, 25 Aug 2023 11:34:34 +0300 Subject: [PATCH 3/6] Aritcle page presentation, domain and data layer implimentations which does not include bloc --- .../remote_remote_data_source.dart | 41 +++ .../blog/data/models/article_model.dart | 81 ++++++ .../data/models/create_article_model.dart | 64 +++++ .../article_repository_implimentation.dart | 52 ++++ .../blog/domain/entities/article_enitity.dart | 24 ++ .../entities/create_article_entity.dart | 18 ++ .../repositories/article_repository.dart | 10 + .../blog/domain/usecases/create_article.dart | 16 ++ .../blog/domain/usecases/update_article.dart | 16 ++ .../blog/presentation/pages/create_blog.dart | 254 ++++++++++++++++++ .../user_repository_implementaion.dart | 7 +- 11 files changed, 579 insertions(+), 4 deletions(-) create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/data/datasources/remote_remote_data_source.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/data/models/article_model.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/data/models/create_article_model.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/data/repository/article_repository_implimentation.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/article_enitity.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/create_article_entity.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/domain/repositories/article_repository.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/create_article.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/update_article.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/presentation/pages/create_blog.dart 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())); } } } From b93f1d9352cd54a9705283fc595866d261bd5314 Mon Sep 17 00:00:00 2001 From: fikremariamF <91600690+fikremariamF@users.noreply.github.com> Date: Mon, 28 Aug 2023 20:42:46 +0300 Subject: [PATCH 4/6] feat(AAiT-mobile-1): Article rebased --- .../remote_remote_data_source.dart | 0 .../data/models/article_model.dart | 0 .../data/models/create_article_model.dart | 0 .../article_repository_implimentation.dart | 8 +- .../domain/entities/article_enitity.dart | 0 .../entities/create_article_entity.dart | 0 .../repositories/article_repository.dart | 0 .../domain/usecases/create_article.dart | 0 .../domain/usecases/update_article.dart | 4 +- .../bloc/article_bloc/article_bloc.dart | 11 +- .../bloc/article_bloc/article_event.dart | 2 + .../bloc/article_bloc/article_state.dart | 0 .../presentation/pages/create_article.dart | 243 +++++++++++++++++ .../presentation/pages/update_article.dart | 237 ++++++++++++++++ .../blog/presentation/pages/create_blog.dart | 254 ------------------ .../blog_app/lib/injection_container.dart | 8 +- 16 files changed, 497 insertions(+), 270 deletions(-) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/data/datasources/remote_remote_data_source.dart (100%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/data/models/article_model.dart (100%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/data/models/create_article_model.dart (100%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/data/repository/article_repository_implimentation.dart (82%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/domain/entities/article_enitity.dart (100%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/domain/entities/create_article_entity.dart (100%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/domain/repositories/article_repository.dart (100%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/domain/usecases/create_article.dart (100%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/domain/usecases/update_article.dart (69%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/presentation/bloc/article_bloc/article_bloc.dart (76%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/presentation/bloc/article_bloc/article_event.dart (73%) rename aait/mobile/group-1/blog_app/lib/features/{blog => Article}/presentation/bloc/article_bloc/article_state.dart (100%) create mode 100644 aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart create mode 100644 aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/update_article.dart delete mode 100644 aait/mobile/group-1/blog_app/lib/features/blog/presentation/pages/create_blog.dart 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/Article/data/datasources/remote_remote_data_source.dart similarity index 100% rename from aait/mobile/group-1/blog_app/lib/features/blog/data/datasources/remote_remote_data_source.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/data/datasources/remote_remote_data_source.dart 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/Article/data/models/article_model.dart similarity index 100% rename from aait/mobile/group-1/blog_app/lib/features/blog/data/models/article_model.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/data/models/article_model.dart 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/Article/data/models/create_article_model.dart similarity index 100% rename from aait/mobile/group-1/blog_app/lib/features/blog/data/models/create_article_model.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/data/models/create_article_model.dart 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/Article/data/repository/article_repository_implimentation.dart similarity index 82% rename from aait/mobile/group-1/blog_app/lib/features/blog/data/repository/article_repository_implimentation.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/data/repository/article_repository_implimentation.dart index c26aaed59..47bed418d 100644 --- 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/Article/data/repository/article_repository_implimentation.dart @@ -1,9 +1,9 @@ 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:blog_app/features/Article/data/datasources/remote_remote_data_source.dart'; +import 'package:blog_app/features/Article/data/models/create_article_model.dart'; +import 'package:blog_app/features/Article/domain/entities/article_enitity.dart'; +import 'package:blog_app/features/Article/domain/repositories/article_repository.dart'; import 'package:dartz/dartz.dart'; import '../../domain/entities/create_article_entity.dart'; 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/Article/domain/entities/article_enitity.dart similarity index 100% rename from aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/article_enitity.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/domain/entities/article_enitity.dart 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/Article/domain/entities/create_article_entity.dart similarity index 100% rename from aait/mobile/group-1/blog_app/lib/features/blog/domain/entities/create_article_entity.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/domain/entities/create_article_entity.dart 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/Article/domain/repositories/article_repository.dart similarity index 100% rename from aait/mobile/group-1/blog_app/lib/features/blog/domain/repositories/article_repository.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/domain/repositories/article_repository.dart 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/Article/domain/usecases/create_article.dart similarity index 100% rename from aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/create_article.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/create_article.dart 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/Article/domain/usecases/update_article.dart similarity index 69% rename from aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/update_article.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/update_article.dart index e1d995c3d..717955419 100644 --- a/aait/mobile/group-1/blog_app/lib/features/blog/domain/usecases/update_article.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/update_article.dart @@ -1,6 +1,6 @@ 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:blog_app/features/Article/domain/entities/article_enitity.dart'; +import 'package:blog_app/features/Article/domain/repositories/article_repository.dart'; import 'package:dartz/dartz.dart'; import '../entities/create_article_entity.dart'; diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_bloc.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_bloc.dart similarity index 76% rename from aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_bloc.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_bloc.dart index 6c5895c2f..edcf212d0 100644 --- a/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_bloc.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_bloc.dart @@ -1,5 +1,5 @@ -import 'package:blog_app/features/blog/domain/usecases/create_article.dart'; -import 'package:blog_app/features/blog/domain/usecases/update_article.dart'; +import 'package:blog_app/features/Article/domain/usecases/create_article.dart'; +import 'package:blog_app/features/Article/domain/usecases/update_article.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../../core/network/network_info.dart'; @@ -20,13 +20,12 @@ class ArticleBloc extends Bloc { on((CreateArticleEvent event, Emitter emit) async { CreateArticle createArticle = CreateArticle(repository); - }); - on((UpdateArticleEvent event, Emitter emit) async { - UpdateArticle updateArticle = UpdateArticle(repository); - + UpdateArticle updateArticle = UpdateArticle(repository); }); + + on((GetArticleEvent event, Emitter emit) async {}); } } diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_event.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart similarity index 73% rename from aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_event.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart index 1e6e920bb..2ae91c6e6 100644 --- a/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_event.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart @@ -5,3 +5,5 @@ abstract class ArticleEvent {} class CreateArticleEvent extends ArticleEvent {} class UpdateArticleEvent extends ArticleEvent {} + +class GetArticleEvent extends ArticleEvent {} diff --git a/aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_state.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_state.dart similarity index 100% rename from aait/mobile/group-1/blog_app/lib/features/blog/presentation/bloc/article_bloc/article_state.dart rename to aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_state.dart diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart new file mode 100644 index 000000000..929b4eafc --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart @@ -0,0 +1,243 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../bloc/article_bloc/article_bloc.dart'; +import '../bloc/article_bloc/article_state.dart'; + +class ArticlePage extends StatefulWidget { + const ArticlePage({super.key, this.id}); + final String? id; + + @override + State createState() => _ArticlePageState(); +} + +class _ArticlePageState extends State { + + @override + void initState() { + 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 (widget.id != null) { + + } + 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() + ], + ), + ) + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/update_article.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/update_article.dart new file mode 100644 index 000000000..e5bea45bd --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/update_article.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../bloc/article_bloc/article_bloc.dart'; +import '../bloc/article_bloc/article_state.dart'; + +class UpdateArticlePage extends StatefulWidget { + const UpdateArticlePage({super.key}); + + @override + State createState() => _UpdateArticlePageState(); +} + +class _UpdateArticlePageState extends State { + @override + void initState() { + 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) { + 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() + ], + ), + ) + ], + ), + ), + ); + }, + ), + ); + } +} 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 deleted file mode 100644 index 91cc5914e..000000000 --- a/aait/mobile/group-1/blog_app/lib/features/blog/presentation/pages/create_blog.dart +++ /dev/null @@ -1,254 +0,0 @@ -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/injection_container.dart b/aait/mobile/group-1/blog_app/lib/injection_container.dart index dbf832420..6956e0ed9 100644 --- a/aait/mobile/group-1/blog_app/lib/injection_container.dart +++ b/aait/mobile/group-1/blog_app/lib/injection_container.dart @@ -1,6 +1,6 @@ -import 'package:blog_app/features/blog/domain/usecases/create_article.dart'; -import 'package:blog_app/features/blog/domain/usecases/update_article.dart'; -import 'package:blog_app/features/blog/presentation/bloc/article_bloc/article_bloc.dart'; +import 'package:blog_app/features/Article/domain/usecases/create_article.dart'; +import 'package:blog_app/features/Article/domain/usecases/update_article.dart'; +import 'package:blog_app/features/Article/presentation/bloc/article_bloc/article_bloc.dart'; import 'package:blog_app/features/user_profile/data/repository/user_repository_implementaion.dart'; import 'package:blog_app/features/user_profile/domain/repositories/user_repository.dart'; import 'package:blog_app/features/user_profile/domain/usecases/get_user_info.dart'; @@ -11,7 +11,7 @@ import 'package:internet_connection_checker/internet_connection_checker.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'core/network/network_info.dart'; -import 'features/blog/data/datasources/remote_remote_data_source.dart'; +import 'features/Article/data/datasources/remote_remote_data_source.dart'; import 'features/user_profile/data/datasources/profile_local_data_source.dart'; import 'features/user_profile/data/datasources/proile_remote_data_source.dart'; import 'features/user_profile/domain/entities/user_entity.dart'; From aa3ba7cd1f1546ce92ad2dbb62e0b538830f78b5 Mon Sep 17 00:00:00 2001 From: fikremariamF <91600690+fikremariamF@users.noreply.github.com> Date: Mon, 28 Aug 2023 22:00:37 +0300 Subject: [PATCH 5/6] get, create and update article implimented --- .../remote_remote_data_source.dart | 13 ++++++- .../article_repository_implimentation.dart | 14 +++++++ .../repositories/article_repository.dart | 1 + .../domain/usecases/create_article.dart | 2 +- .../Article/domain/usecases/get_article.dart | 14 +++++++ .../domain/usecases/update_article.dart | 2 +- .../bloc/article_bloc/article_bloc.dart | 18 +++++++-- .../bloc/article_bloc/article_event.dart | 18 +++++++-- .../bloc/article_bloc/article_state.dart | 6 ++- .../presentation/pages/create_article.dart | 37 ++++++++++++++++++- 10 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/get_article.dart diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/data/datasources/remote_remote_data_source.dart b/aait/mobile/group-1/blog_app/lib/features/Article/data/datasources/remote_remote_data_source.dart index 0614557ea..8a3108c9d 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/data/datasources/remote_remote_data_source.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/data/datasources/remote_remote_data_source.dart @@ -9,6 +9,7 @@ import '../models/create_article_model.dart'; abstract class ArticleRemoteDataSource { Future postArticle(CreateArticleModel articleModel); Future updateArticle(CreateArticleModel articleModel); + Future getArticle(String id); } class ArticleRemoteDataSourceImpl extends ArticleRemoteDataSource { @@ -26,14 +27,22 @@ class ArticleRemoteDataSourceImpl extends ArticleRemoteDataSource { Future updateArticle(CreateArticleModel articleModel) async { final String? token = await getToken(); final id = articleModel.id; - final response = await http.post( - Uri.parse('$baseApi/article/$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)); } + @override + Future getArticle(String id) async { + final String? token = await getToken(); + final response = await http.get(Uri.parse('$baseApi/article/$id'), + headers: {'Content-Type': 'application/json', "token": token!}); + + 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/Article/data/repository/article_repository_implimentation.dart b/aait/mobile/group-1/blog_app/lib/features/Article/data/repository/article_repository_implimentation.dart index 47bed418d..0527fb144 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/data/repository/article_repository_implimentation.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/data/repository/article_repository_implimentation.dart @@ -49,4 +49,18 @@ class ArticleRepositoryImpl extends ArticleRepository { ConnectionFailure(message: "Failed to connect to the ethernet")); } } + + + @override + Future> getArticle( + String id) async { + final isConnected = await networkInfo.isConnected; + if (isConnected) { + final article = await remoteDataSource.getArticle(id); + 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/Article/domain/repositories/article_repository.dart b/aait/mobile/group-1/blog_app/lib/features/Article/domain/repositories/article_repository.dart index 5b4bcfa2f..6c8f997fc 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/domain/repositories/article_repository.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/domain/repositories/article_repository.dart @@ -7,4 +7,5 @@ import '../entities/create_article_entity.dart'; abstract class ArticleRepository { Future> createArticle(CreateArticleEntity article); Future> updateArticle(CreateArticleEntity article); + Future> getArticle(String id); } diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/create_article.dart b/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/create_article.dart index d4c5a33d3..d84701927 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/create_article.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/create_article.dart @@ -9,7 +9,7 @@ class CreateArticle { final ArticleRepository repository; CreateArticle(this.repository); - Future> createArticle(CreateArticleEntity article) async { + Future> use(CreateArticleEntity article) async { return await repository.createArticle(article); } } diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/get_article.dart b/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/get_article.dart new file mode 100644 index 000000000..9411d2d6b --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/get_article.dart @@ -0,0 +1,14 @@ +import 'package:blog_app/core/errors/failures/failure.dart'; +import 'package:blog_app/features/Article/domain/entities/article_enitity.dart'; +import 'package:blog_app/features/Article/domain/repositories/article_repository.dart'; +import 'package:dartz/dartz.dart'; + +class GetArticle { + final ArticleRepository repository; + GetArticle(this.repository); + + Future> use( + String id) async { + return await repository.getArticle(id); + } +} diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/update_article.dart b/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/update_article.dart index 717955419..1f5c7e306 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/update_article.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/domain/usecases/update_article.dart @@ -9,7 +9,7 @@ class UpdateArticle { final ArticleRepository repository; UpdateArticle(this.repository); - Future> updateArticle(CreateArticleEntity article) async { + Future> use(CreateArticleEntity article) async { return await repository.updateArticle(article); } } diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_bloc.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_bloc.dart index 72be926a4..61c04ff2a 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_bloc.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_bloc.dart @@ -1,4 +1,6 @@ +import 'package:blog_app/features/Article/domain/entities/article_enitity.dart'; import 'package:blog_app/features/Article/domain/usecases/create_article.dart'; +import 'package:blog_app/features/Article/domain/usecases/get_article.dart'; import 'package:blog_app/features/Article/domain/usecases/update_article.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -19,13 +21,23 @@ class ArticleBloc extends Bloc { networkInfo: networkInfo, remoteDataSource: remoteDataSource); on((CreateArticleEvent event, Emitter emit) async { - CreateArticle createArticle = CreateArticle(repository); + CreateArticle usecase = CreateArticle(repository); + await usecase.repository.createArticle(event.article); }); on((UpdateArticleEvent event, Emitter emit) async { - UpdateArticle updateArticle = UpdateArticle(repository); + UpdateArticle usecase = UpdateArticle(repository); + await usecase.repository.updateArticle(event.article); }); - on((GetArticleEvent event, Emitter emit) async {}); + on((GetArticleEvent event, Emitter emit) async { + GetArticle usecase = GetArticle(repository); + final article = await usecase.repository.getArticle(event.id); + if (article is Article) { + emit(ArticleFetched(article as Article)); + } else { + emit(Error()); + } + }); } } diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart index 2ae91c6e6..ad35cd897 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart @@ -1,9 +1,19 @@ - +import 'package:blog_app/features/Article/domain/entities/create_article_entity.dart'; +import 'package:blog_app/features/Article/domain/usecases/create_article.dart'; abstract class ArticleEvent {} -class CreateArticleEvent extends ArticleEvent {} +class CreateArticleEvent extends ArticleEvent { + CreateArticleEntity article; + CreateArticleEvent(this.article); +} -class UpdateArticleEvent extends ArticleEvent {} +class UpdateArticleEvent extends ArticleEvent { + CreateArticleEntity article; + UpdateArticleEvent(this.article); +} -class GetArticleEvent extends ArticleEvent {} +class GetArticleEvent extends ArticleEvent { + String id; + GetArticleEvent(this.id); +} diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_state.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_state.dart index 4d456d39c..d53e2a3a0 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_state.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_state.dart @@ -1,3 +1,4 @@ +import 'package:blog_app/features/Article/domain/entities/article_enitity.dart'; abstract class ArticleState {} @@ -7,4 +8,7 @@ class Error extends ArticleState {} class Loading extends ArticleState {} - +class ArticleFetched extends ArticleState { + Article article; + ArticleFetched(this.article); +} diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart index 929b4eafc..c18948501 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart @@ -1,3 +1,7 @@ +import 'package:blog_app/features/Article/domain/entities/article_enitity.dart'; +import 'package:blog_app/features/Article/domain/entities/create_article_entity.dart'; +import 'package:blog_app/features/Article/domain/usecases/create_article.dart'; +import 'package:blog_app/features/Article/presentation/bloc/article_bloc/article_event.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -13,7 +17,6 @@ class ArticlePage extends StatefulWidget { } class _ArticlePageState extends State { - @override void initState() { super.initState(); @@ -43,6 +46,7 @@ class _ArticlePageState extends State { @override Widget build(BuildContext context) { + Article? initialArticle; bool tonalSelected = true; final formKey = GlobalKey(); @@ -55,7 +59,19 @@ class _ArticlePageState extends State { body: BlocBuilder( builder: (context, state) { if (widget.id != null) { - + final getArticle = BlocProvider.of(context); + getArticle.add(GetArticleEvent(widget.id!)); + } + if (state is ArticleFetched) { + setState(() { + initialArticle = state.article; + titleController.text = state.article.title; + subTitleController.text = state.article.subTitle; + articleContent.text = state.article.subTitle; + for (String tag in state.article.tags) { + tagsMap[tag] = true; + } + }); } return SafeArea( child: Container( @@ -206,6 +222,23 @@ class _ArticlePageState extends State { if (!formValid) { return; } + + final bloc = BlocProvider.of(context); + final tags = tagsMap.keys + .where((key) => tagsMap[key]!) + .toList(); + CreateArticleEntity article = CreateArticleEntity( + id: initialArticle!.id, + title: titleController.text, + subTitle: subTitleController.text, + tags: tags, + content: articleContent.text); + if (widget.id != null && initialArticle != null) { + bloc.add(UpdateArticleEvent(article)); + } else { + bloc.add(CreateArticleEvent(article)); + } + Navigator.pop(context); }, style: ButtonStyle( backgroundColor: MaterialStateProperty.all( From e38875fc8a96e91ba6cc5b6d5bdb70bae91ae632 Mon Sep 17 00:00:00 2001 From: fikremariamF <91600690+fikremariamF@users.noreply.github.com> Date: Mon, 28 Aug 2023 22:11:13 +0300 Subject: [PATCH 6/6] minor changes --- .../Article/presentation/bloc/article_bloc/article_event.dart | 1 - .../lib/features/Article/presentation/pages/create_article.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart index ad35cd897..915d655f6 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/bloc/article_bloc/article_event.dart @@ -1,5 +1,4 @@ import 'package:blog_app/features/Article/domain/entities/create_article_entity.dart'; -import 'package:blog_app/features/Article/domain/usecases/create_article.dart'; abstract class ArticleEvent {} diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart index c18948501..3019955d7 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/create_article.dart @@ -1,6 +1,5 @@ import 'package:blog_app/features/Article/domain/entities/article_enitity.dart'; import 'package:blog_app/features/Article/domain/entities/create_article_entity.dart'; -import 'package:blog_app/features/Article/domain/usecases/create_article.dart'; import 'package:blog_app/features/Article/presentation/bloc/article_bloc/article_event.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart';