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/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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'; From 0505301ebe3ead2cd34e0e70f04af259a5524e5f Mon Sep 17 00:00:00 2001 From: fikremariamF <91600690+fikremariamF@users.noreply.github.com> Date: Tue, 29 Aug 2023 10:35:37 +0300 Subject: [PATCH 7/9] changes --- .../lib/Injection/article_injection.dart | 45 ++++ .../remote_remote_data_source.dart | 4 +- .../bloc/article_bloc/article_bloc.dart | 2 +- .../presentation/pages/create_article.dart | 10 +- .../presentation/pages/update_article.dart | 237 ------------------ 5 files changed, 54 insertions(+), 244 deletions(-) create mode 100644 aait/mobile/group-1/blog_app/lib/Injection/article_injection.dart delete mode 100644 aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/update_article.dart diff --git a/aait/mobile/group-1/blog_app/lib/Injection/article_injection.dart b/aait/mobile/group-1/blog_app/lib/Injection/article_injection.dart new file mode 100644 index 000000000..44f0e87b8 --- /dev/null +++ b/aait/mobile/group-1/blog_app/lib/Injection/article_injection.dart @@ -0,0 +1,45 @@ +// import 'package:blog_app/features/Article/domain/usecases/get_article.dart'; +// import 'package:blog_app/features/Article/presentation/bloc/article_bloc/article_bloc.dart'; +// import 'package:blog_app/features/blog/data/datasources/remote_remote_data_source.dart'; +// import 'package:blog_app/features/blog/data/repository/article_repository_implimentation.dart'; +// import 'package:blog_app/features/blog/domain/entities/create_article_entity.dart'; +// import 'package:blog_app/features/blog/domain/repositories/article_repository.dart'; +// import 'package:blog_app/features/blog/domain/usecases/create_article.dart'; +// import 'package:blog_app/features/blog/domain/usecases/update_article.dart'; +// import 'package:get_it/get_it.dart'; +// import 'package:internet_connection_checker/internet_connection_checker.dart'; +// import 'package:shared_preferences/shared_preferences.dart'; + +// import '../core/network/network_info.dart'; +// import '../features/Article/domain/entities/article_enitity.dart'; + +// final sl = GetIt.instance; + +// Future init() async { +// //! Features - Task Management +// // Bloc +// sl.registerFactory(() => ArticleBloc()); + +// // Use cases +// sl.registerLazySingleton(() => GetArticle(sl())); +// sl.registerLazySingleton(() => CreateArticle(sl())); +// sl.registerLazySingleton(() => UpdateArticle(sl())); + +// // Repository +// sl.registerLazySingleton( +// () => ArticleRemoteDataSourceImpl()); +// sl.registerLazySingleton( +// () => ArticleRepositoryImpl(networkInfo: sl(), remoteDataSource: sl())); + +// // classes +// sl.registerLazySingleton(() =>
[]); +// sl.registerLazySingleton(() => []); +// //! Core +// sl.registerLazySingleton(() => NetworkInfoImpl(sl())); + +// //! External +// sl.registerLazySingleton(() => InternetConnectionChecker()); + +// final sharedPreferences = await SharedPreferences.getInstance(); +// sl.registerLazySingleton(() => sharedPreferences); +// } 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 8a3108c9d..4a43c0a6e 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 @@ -15,7 +15,9 @@ abstract class ArticleRemoteDataSource { class ArticleRemoteDataSourceImpl extends ArticleRemoteDataSource { @override Future postArticle(CreateArticleModel articleModel) async { - final String? token = await getToken(); + String? token = await getToken(); + token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0ZWNmMzgzODk5NDQ1M2ZhMzkxZTIxMSIsImlhdCI6MTY5MzI1MDQ1MiwiZXhwIjoxNjk1ODQyNDUyfQ.ZgYBMdKyBgVLIxFnZ7CX5Rrg4jVIjj6fG6O_SoWhwk0"; final response = await http.post(Uri.parse('$baseApi/article'), headers: {'Content-Type': 'application/json', "token": token!}, body: json.encode(articleModel.toJson())); 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 61c04ff2a..4ff4f39f8 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 @@ -4,7 +4,7 @@ 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'; -import '../../../../../Injection/injection_container.dart'; +import '../../../../../Injection/article_injection.dart'; import '../../../../../core/network/network_info.dart'; import '../../../data/datasources/remote_remote_data_source.dart'; import '../../../data/repository/article_repository_implimentation.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 index 3019955d7..ede68dc30 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 @@ -8,18 +8,18 @@ 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; + const ArticlePage({this.id}); @override State createState() => _ArticlePageState(); } class _ArticlePageState extends State { - @override - void initState() { - super.initState(); - } + // @override + // void initState() { + // super.initState(); + // } List tags = [ "Sports", 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 deleted file mode 100644 index e5bea45bd..000000000 --- a/aait/mobile/group-1/blog_app/lib/features/Article/presentation/pages/update_article.dart +++ /dev/null @@ -1,237 +0,0 @@ -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() - ], - ), - ) - ], - ), - ), - ); - }, - ), - ); - } -} From 4eed862287146d80f167606efb9c0a9a03fd4b9a Mon Sep 17 00:00:00 2001 From: fikremariamF <91600690+fikremariamF@users.noreply.github.com> Date: Tue, 29 Aug 2023 17:24:44 +0300 Subject: [PATCH 8/9] changes --- .../remote_remote_data_source.dart | 75 ++- .../Article/data/models/article_model.dart | 12 +- .../data/models/create_article_model.dart | 12 +- .../article_repository_implimentation.dart | 8 +- .../domain/entities/article_enitity.dart | 8 +- .../entities/create_article_entity.dart | 8 +- .../bloc/article_bloc/article_bloc.dart | 4 + .../presentation/pages/create_article.dart | 581 ++++++++++++------ 8 files changed, 472 insertions(+), 236 deletions(-) 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 4a43c0a6e..28cfe90f6 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 @@ -1,10 +1,13 @@ import 'dart:convert'; +import 'dart:io'; 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'; +import 'package:http_parser/http_parser.dart'; +import 'package:mime/mime.dart'; abstract class ArticleRemoteDataSource { Future postArticle(CreateArticleModel articleModel); @@ -16,24 +19,74 @@ class ArticleRemoteDataSourceImpl extends ArticleRemoteDataSource { @override Future postArticle(CreateArticleModel articleModel) async { String? token = await getToken(); - token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0ZWNmMzgzODk5NDQ1M2ZhMzkxZTIxMSIsImlhdCI6MTY5MzI1MDQ1MiwiZXhwIjoxNjk1ODQyNDUyfQ.ZgYBMdKyBgVLIxFnZ7CX5Rrg4jVIjj6fG6O_SoWhwk0"; - final response = await http.post(Uri.parse('$baseApi/article'), - headers: {'Content-Type': 'application/json', "token": token!}, - body: json.encode(articleModel.toJson())); + final uri = Uri.parse('$baseApi/article'); + var request = http.MultipartRequest('POST', uri); - return ArticleModel.fromJson(jsonDecode(response.body)); + Map headers = { + HttpHeaders.authorizationHeader: + 'Bearer $token', // Add your authentication token here + }; + + request.headers.addAll(headers); + File imageFile = File(articleModel.image); + final String fileType = MimeTypeResolver().lookup(imageFile.path) ?? ""; + + request.files.add(http.MultipartFile( + 'photo', imageFile.readAsBytes().asStream(), imageFile.lengthSync(), + filename: imageFile.path.split("/").last, + contentType: MediaType.parse(fileType))); + + request.fields['title'] = articleModel.title; + request.fields['subTitle'] = articleModel.subTitle; + request.fields['tags'] = jsonEncode(articleModel.tags); + request.fields['content'] = articleModel.content; + request.fields['estimatedReadTime'] = articleModel.estimatedtime; + + var response = await request.send(); + + if (response.statusCode == 200) { + return articleModel as ArticleModel; + } + throw ("Couldn't Post Article ${response.statusCode}"); } @override Future updateArticle(CreateArticleModel articleModel) async { - final String? token = await getToken(); + String? token = await getToken(); + print("something wrong"); + if (articleModel.id == null) { + throw ("Couldn't Post Article"); + } 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())); + final uri = Uri.parse('$baseApi/article/$id'); + var request = http.MultipartRequest('PUT', uri); - return ArticleModel.fromJson(jsonDecode(response.body)); + Map headers = { + HttpHeaders.authorizationHeader: + 'Bearer $token', // Add your authentication token here + }; + + request.headers.addAll(headers); + File imageFile = File(articleModel.image); + final String fileType = MimeTypeResolver().lookup(imageFile.path) ?? ""; + + request.files.add(http.MultipartFile( + 'photo', imageFile.readAsBytes().asStream(), imageFile.lengthSync(), + filename: imageFile.path.split("/").last, + contentType: MediaType.parse(fileType))); + + request.fields['title'] = articleModel.title; + request.fields['subTitle'] = articleModel.subTitle; + request.fields['tags'] = jsonEncode(articleModel.tags); + request.fields['content'] = articleModel.content; + request.fields['estimatedReadTime'] = articleModel.estimatedtime; + + var response = await request.send(); + + if (response.statusCode == 200) { + return articleModel as ArticleModel; + } + throw ("Couldn't Post Article ${response.statusCode}"); } @override diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/data/models/article_model.dart b/aait/mobile/group-1/blog_app/lib/features/Article/data/models/article_model.dart index 49cffd156..e89484892 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/data/models/article_model.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/data/models/article_model.dart @@ -12,8 +12,8 @@ class ArticleModel extends Article implements Equatable { required this.tags, this.user, required this.content, - this.image, - this.estimatedtime, + required this.image, + required this.estimatedtime, this.imageCloudinaryPublicId, this.createdAt}) : super( @@ -42,9 +42,9 @@ class ArticleModel extends Article implements Equatable { @override final String content; @override - final String? image; + final String image; @override - final String? estimatedtime; + final String estimatedtime; @override final String? imageCloudinaryPublicId; @override @@ -69,7 +69,9 @@ class ArticleModel extends Article implements Equatable { 'title': articleModel.title, 'subTitle': articleModel.subTitle, 'content': articleModel.content, - 'tags': jsonEncode(articleModel.tags) + 'tags': jsonEncode(articleModel.tags), + 'image': articleModel.image, + 'estimatedReadTime': articleModel.estimatedtime }; } diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/data/models/create_article_model.dart b/aait/mobile/group-1/blog_app/lib/features/Article/data/models/create_article_model.dart index b2909459a..5db2617ea 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/data/models/create_article_model.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/data/models/create_article_model.dart @@ -11,8 +11,8 @@ class CreateArticleModel extends CreateArticleEntity implements Equatable { required this.tags, required this.content, this.id, - this.image, - this.estimatedtime, + required this.image, + required this.estimatedtime, }) : super( title: title, subTitle: subTitle, @@ -33,9 +33,9 @@ class CreateArticleModel extends CreateArticleEntity implements Equatable { @override final String content; @override - final String? image; + final String image; @override - final String? estimatedtime; + final String estimatedtime; factory CreateArticleModel.fromJson(Map json) { return CreateArticleModel( @@ -52,7 +52,9 @@ class CreateArticleModel extends CreateArticleEntity implements Equatable { 'title': title, 'subTitle': subTitle, 'content': content, - 'tags': tags + 'tags': tags, + 'image': image, + 'estimatedReadTime': estimatedtime }; } 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 0527fb144..707dd5eb0 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 @@ -22,7 +22,9 @@ class ArticleRepositoryImpl extends ArticleRepository { title: article.title, subTitle: article.subTitle, tags: article.tags, - content: article.content); + content: article.content, + image: article.image, + estimatedtime: article.estimatedtime); final isConnected = await networkInfo.isConnected; if (isConnected) { final article = await remoteDataSource.postArticle(createArticleModel); @@ -39,7 +41,9 @@ class ArticleRepositoryImpl extends ArticleRepository { title: article.title, subTitle: article.subTitle, tags: article.tags, - content: article.content); + content: article.content, + image: article.image, + estimatedtime: article.estimatedtime); final isConnected = await networkInfo.isConnected; if (isConnected) { final article = await remoteDataSource.updateArticle(createArticleModel); diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/domain/entities/article_enitity.dart b/aait/mobile/group-1/blog_app/lib/features/Article/domain/entities/article_enitity.dart index 3877ff705..20596fc7c 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/domain/entities/article_enitity.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/domain/entities/article_enitity.dart @@ -5,8 +5,8 @@ class Article { final String? user; final List tags; final String content; - final String? image; - final String? estimatedtime; + final String image; + final String estimatedtime; final String? imageCloudinaryPublicId; final DateTime? createdAt; @@ -17,8 +17,8 @@ class Article { required this.tags, this.user, required this.content, - this.image, - this.estimatedtime, + required this.image, + required this.estimatedtime, this.imageCloudinaryPublicId, this.createdAt}); } diff --git a/aait/mobile/group-1/blog_app/lib/features/Article/domain/entities/create_article_entity.dart b/aait/mobile/group-1/blog_app/lib/features/Article/domain/entities/create_article_entity.dart index 8e63111cd..d980247e9 100644 --- a/aait/mobile/group-1/blog_app/lib/features/Article/domain/entities/create_article_entity.dart +++ b/aait/mobile/group-1/blog_app/lib/features/Article/domain/entities/create_article_entity.dart @@ -4,8 +4,8 @@ class CreateArticleEntity { final String subTitle; final List tags; final String content; - final String? image; - final String? estimatedtime; + final String image; + final String estimatedtime; CreateArticleEntity( {this.id, @@ -13,6 +13,6 @@ class CreateArticleEntity { required this.subTitle, required this.tags, required this.content, - this.image, - this.estimatedtime}); + required this.image, + required this.estimatedtime}); } 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 2ac46961e..3e38fb183 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 @@ -22,12 +22,16 @@ class ArticleBloc extends Bloc { on((CreateArticleEvent event, Emitter emit) async { CreateArticle usecase = CreateArticle(repository); + emit(Loading()); await usecase.repository.createArticle(event.article); + emit(Idle()); }); on((UpdateArticleEvent event, Emitter emit) async { UpdateArticle usecase = UpdateArticle(repository); + emit(Loading()); await usecase.repository.updateArticle(event.article); + emit(Idle()); }); on((GetArticleEvent event, Emitter emit) async { 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..3e1fe5b07 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 @@ -5,11 +5,15 @@ import 'package:blog_app/features/Article/presentation/bloc/article_bloc/article import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../core/utils/colors.dart'; +import '../../../authentication_and_authorization/presentation/pages/circular_indicator.dart'; import '../bloc/article_bloc/article_bloc.dart'; import '../bloc/article_bloc/article_state.dart'; +import 'package:image_picker/image_picker.dart'; +import 'dart:io'; class ArticlePage extends StatefulWidget { - const ArticlePage({super.key, this.id}); + const ArticlePage({super.key, this.id = "64eda97cb00b3d3d2fe86ec5"}); final String? id; @override @@ -23,38 +27,42 @@ class _ArticlePageState extends State { } List tags = [ - "Sports", - "Tech", - "Politics", - "Art", - "Design", - "Culture", - "Production", - "Others", + "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, + "sports": false, + "tech": false, + "politics": false, + "art": false, + "design": false, + "culture": false, + "production": false, + "others": false, }; - @override - Widget build(BuildContext context) { - Article? initialArticle; - bool tonalSelected = true; + Article? initialArticle; + bool tonalSelected = true; - final formKey = GlobalKey(); + final formKey = GlobalKey(); - final titleController = TextEditingController(); - final subTitleController = TextEditingController(); - final articleContent = TextEditingController(); + final titleController = TextEditingController(); + final subTitleController = TextEditingController(); + final articleContent = TextEditingController(); + final estimatedReadTimeContoller = TextEditingController(); + final ImagePicker picker = ImagePicker(); + Widget articleImage = Icon(Icons.place); + var imagePath; + @override + Widget build(BuildContext context) { return Scaffold( body: BlocBuilder( builder: (context, state) { @@ -68,207 +76,370 @@ class _ArticlePageState extends State { titleController.text = state.article.title; subTitleController.text = state.article.subTitle; articleContent.text = state.article.subTitle; + estimatedReadTimeContoller.text = state.article.estimatedtime; for (String tag in state.article.tags) { tagsMap[tag] = true; } }); } - 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(); + if (state is Idle) { + return SafeArea( + child: Container( + width: MediaQuery.of(context).size.width, + 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), + ), + ], + ), + Container( + width: MediaQuery.of(context).size.width * 0.5, + margin: const EdgeInsets.only(top: 5, bottom: 10), + child: TextButton( + onPressed: () async { + await showDialog( + context: context, + builder: (BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.only(bottom: 8), + child: TextButton( + style: ButtonStyle( + padding: + const MaterialStatePropertyAll( + EdgeInsets.symmetric( + vertical: 15, + horizontal: 25)), + backgroundColor: + MaterialStatePropertyAll( + Colors.grey[200])), + onPressed: () async { + final XFile? image = + await picker.pickImage( + source: + ImageSource.gallery); + + setState(() { + articleImage = + Image.file(File(image!.path)); + imagePath = image.path; + }); + }, + child: Text( + "Gallery", + style: TextStyle( + fontSize: 22, + color: Colors.grey[800], + fontWeight: FontWeight.w500, + ), + ), + ), + ), + TextButton( + style: ButtonStyle( + padding: MaterialStatePropertyAll( + EdgeInsets.symmetric( + vertical: 15, + horizontal: 25)), + backgroundColor: + MaterialStatePropertyAll( + Colors.grey[200])), + onPressed: () async { + final XFile? photo = + await picker.pickImage( + source: ImageSource.camera); + + setState(() { + articleImage = + Image.file(File(photo!.path)); + imagePath = photo.path; + }); + }, + child: Text( + "Camera", + style: TextStyle( + fontSize: 22, + color: Colors.grey[800], + fontWeight: FontWeight.w500, + ), + ), + ), + ]); + }); }, + child: Stack( + alignment: Alignment.center, + children: [ + SizedBox( + width: 80, height: 80, child: articleImage), + Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + width: 92, + height: 92, + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, color: kLightBlue), + borderRadius: BorderRadius.circular(28), + ), + ), + ), + ], + ), + ], + ), ), - 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), + ), + 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; + }, ), - 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), + 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; + }, ), - 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: 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 ), - ); - })), - ), - 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), + validator: (String? name) { + if (name == null || name.isEmpty) { + return "Name can not be empty"; + } + return null; + }, ), - 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; - } - - 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)); + Padding( + padding: const EdgeInsets.symmetric( + vertical: 7, horizontal: 15), + child: Column(children: [ + const Text("Estimated read time:"), + TextFormField( + controller: articleContent, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(10.0)), + borderSide: + BorderSide(color: Colors.grey, width: 0)), + hintText: "5 min", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 15.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"; } - Navigator.pop(context); + return null; }, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - const Color(0xFF376AED)), - shape: MaterialStateProperty.all< - RoundedRectangleBorder>( - const RoundedRectangleBorder( - borderRadius: - BorderRadius.all(Radius.circular(50)), + ), + ]), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(), + ElevatedButton( + onPressed: () async { + if (formKey.currentState != null) { + final formValid = + formKey.currentState!.validate(); + if (!formValid) { + return; + } + } + + final bloc = + BlocProvider.of(context); + final tags = tagsMap.keys + .where((key) => tagsMap[key]!) + .toList(); + + if (widget.id != null && initialArticle != null) { + CreateArticleEntity article = CreateArticleEntity( + id: initialArticle!.id, + image: imagePath, + title: titleController.text, + subTitle: subTitleController.text, + tags: tags, + content: articleContent.text, + estimatedtime: + "${estimatedReadTimeContoller.text}min"); + bloc.add(UpdateArticleEvent(article)); + } else { + CreateArticleEntity article = + CreateArticleEntity( + // id: initialArticle!.id, + title: titleController.text, + image: imagePath, + subTitle: subTitleController.text, + tags: tags, + content: articleContent.text, + estimatedtime: + "${estimatedReadTimeContoller.text}min"); + bloc.add(CreateArticleEvent(article)); + } + Navigator.pop(context); + }, + 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", + child: const Padding( + padding: EdgeInsets.symmetric( + vertical: 8.0, horizontal: 15.0), + child: Text( + style: TextStyle( + fontSize: 17, color: Colors.white), + "Publish", + ), ), ), - ), - const SizedBox() - ], - ), - ) - ], + const SizedBox() + ], + ), + ) + ], + ), ), - ), - ); + ); + } else { + return const CircularIndicator(); + } }, ), ); From c54254041f88b7767350ccb0ac1da8bdabeabc55 Mon Sep 17 00:00:00 2001 From: fikremariamF <91600690+fikremariamF@users.noreply.github.com> Date: Wed, 30 Aug 2023 09:43:23 +0300 Subject: [PATCH 9/9] changes --- .../features/Article/presentation/pages/create_article.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 3e1fe5b07..6ac3b66e3 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 @@ -13,7 +13,7 @@ import 'package:image_picker/image_picker.dart'; import 'dart:io'; class ArticlePage extends StatefulWidget { - const ArticlePage({super.key, this.id = "64eda97cb00b3d3d2fe86ec5"}); + const ArticlePage({super.key, this.id}); final String? id; @override @@ -329,7 +329,7 @@ class _ArticlePageState extends State { child: Column(children: [ const Text("Estimated read time:"), TextFormField( - controller: articleContent, + controller: estimatedReadTimeContoller, keyboardType: TextInputType.number, decoration: const InputDecoration( border: OutlineInputBorder( @@ -352,7 +352,7 @@ class _ArticlePageState extends State { ), validator: (String? name) { if (name == null || name.isEmpty) { - return "Name can not be empty"; + return "Estimated read can not be empty"; } return null; },