list = await _manhuaManhwaRepo.getManhuaManhwa(
70 | type: 'manhwa',page: currentState.page,
71 | );
72 | yield list.isEmpty ? currentState.copyWith(hasReachedMax: true):
73 | ManhwaLoadedState(list: currentState.list+list,
74 | page: currentState.page+=1,hasReachedMax: false);
75 | }
76 | }catch(e){
77 | yield ManhuaManhwaFailureState(msg: e.toString());
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MangaMint
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | MangaMint is manga reader application that provides manga and comic bahasa indonesia and currently
15 | available on android only, idk in the future will be available on ios or not
16 |
17 | ## Usage
18 | 1. Download or clone this repository [manga-api](https://github.com/febryardiansyah/manga-api)
19 | > git clone https://github.com/febryardiansyah/manga-api
20 | 2. you have to deploy this api by yourself `(You can use heroku,vercel, or glitch)`
21 | 2. change `const String BaseUrl = 'https://10.0.2.2:8000/api/';` (/lib/constants/base_url.dart) to your API. ex: `example.herokuapp.com.com/api/`
22 |
23 | ## Prototype of This App
24 | [MangaMint Protyotype](https://www.figma.com/proto/tEwOEwAIycAuWfMOCffG3w/customDesign?node-id=591%3A3&scaling=scale-down)
25 |
26 | ## Download Apk
27 | [mangamint.apk](https://github.com/febryardiansyah/manga_mint/releases/tag/v.1.0)
28 |
29 | ## Features
30 | - [x] Manga List (Japanase Comic)
31 | - [x] Manhua List (Chinese Comic)
32 | - [x] Manhwa List (Korean Comic)
33 | - [x] Last chapter update
34 | - [x] Bookmarks Manga
35 | - [x] Read last chapter opened
36 | - [x] Search Manga by name and Genres
37 | - [x] Read chapter vertically or horizontally
38 |
39 | ## Screenshot
40 |
41 |
42 |
43 |
44 |
45 | ## Build Setup
46 | ``` bash
47 |
48 | # install dependencies
49 | $ flutter pub get
50 |
51 | # run debug mode
52 | $ flutter run
53 |
54 | # run release mode
55 | $ flutter run --release
56 |
57 | # build app bundle
58 | $ flutter build appbundle
59 |
60 | # build apk
61 | $ flutter build apk
62 |
63 | ```
64 | ## Dependencies that i use
65 | - http: ^0.12.1
66 | - bloc: ^5.0.1
67 | - flutter_bloc: ^5.0.1
68 | - google_fonts: ^1.1.0
69 | - equatable: ^1.2.0
70 | - rxdart: ^0.24.1
71 | - ff_navigation_bar: ^0.1.5
72 | - flutter_spinkit: ^4.1.2+1
73 | - shimmer: ^1.1.1
74 | - carousel_slider: ^2.2.1
75 | - flutter_screenutil: ^2.2.0
76 | - cached_network_image: ^2.2.0+1
77 | - photo_view: ^0.9.2
78 | - shared_preferences: ^0.5.8
79 | - path_provider: ^1.6.11
80 | - path: ^1.7.0
81 | - hive: ^1.4.1+1
82 | - hive_flutter: ^0.2.1
83 | - toast: ^0.1.5
84 | - font_awesome_flutter: ^8.8.1
85 | - url_launcher: ^5.5.0
86 |
87 | For detailed explanation on how things work, check out [Flutter docs](https://flutter.dev/docs).
88 |
89 |
--------------------------------------------------------------------------------
/lib/components/item_big.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:mangamint/components/image_cache_loading.dart';
5 | import 'package:mangamint/constants/base_color.dart';
6 | import 'package:mangamint/helper/color_manga_type.dart';
7 |
8 | class ItemBig extends StatelessWidget {
9 | final itemCount, itemBuilder;
10 | final ScrollController controller;
11 | const ItemBig({Key key, this.itemCount, this.itemBuilder,this.controller}) : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return GridView.builder(
16 | controller: controller,
17 | shrinkWrap: true,
18 | physics: ClampingScrollPhysics(),
19 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
20 | crossAxisCount: 3,
21 | mainAxisSpacing: 10,
22 | crossAxisSpacing: 10,
23 | childAspectRatio: 0.45),
24 | itemCount: itemCount,
25 | itemBuilder: itemBuilder,
26 | );
27 | }
28 | }
29 |
30 | class ItemBigChild extends StatelessWidget {
31 | final String type, thumb, title, chapter, update;
32 | final GestureTapCallback onTap;
33 |
34 | const ItemBigChild(
35 | {Key key, this.type, this.thumb, this.title, this.chapter, this.update,this.onTap})
36 | : super(key: key);
37 |
38 | @override
39 | Widget build(BuildContext context) {
40 | final size = MediaQuery.of(context).size;
41 | ScreenUtil.init();
42 | return InkWell(
43 | onTap: onTap,
44 | child: Column(
45 | crossAxisAlignment: CrossAxisAlignment.start,
46 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
47 | children: [
48 | Stack(
49 | children: [
50 | ClipRRect(
51 | borderRadius: BorderRadius.all(Radius.circular(8)),
52 | child: ImageCacheLoading(
53 | imgUrl: thumb,
54 | imageBuilder: (context,imgProvider){
55 | return Container(
56 | height: 450.h,
57 | width: size.width,
58 | decoration: BoxDecoration(
59 | image: DecorationImage(image: imgProvider,fit: BoxFit.cover),
60 | ),
61 | );
62 | },
63 | ),
64 | ),
65 | Positioned(
66 | bottom: 10,
67 | left: 0,
68 | child: Container(
69 | height: 80.h,
70 | width: 200.w,
71 | color: mangaTypeColor(type),
72 | child: Center(
73 | child: Text(
74 | type,
75 | style: TextStyle(color: Colors.white),
76 | )),
77 | ),
78 | )
79 | ],
80 | ),
81 | Text(
82 | title.length > 20 ? '${title.substring(0, 20)}..' : title,
83 | style: TextStyle(fontWeight: FontWeight.bold),
84 | ),
85 | Text(
86 | chapter,
87 | style: TextStyle(color: BaseColor.green),
88 | ),
89 | Expanded(
90 | child: Text(
91 | update,
92 | style: TextStyle(color: BaseColor.grey1),
93 | ),
94 | )
95 | ],
96 | ),
97 | );
98 | }
99 | }
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def parse_KV_file(file, separator='=')
14 | file_abs_path = File.expand_path(file)
15 | if !File.exists? file_abs_path
16 | return [];
17 | end
18 | generated_key_values = {}
19 | skip_line_start_symbols = ["#", "/"]
20 | File.foreach(file_abs_path) do |line|
21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
22 | plugin = line.split(pattern=separator)
23 | if plugin.length == 2
24 | podname = plugin[0].strip()
25 | path = plugin[1].strip()
26 | podpath = File.expand_path("#{path}", file_abs_path)
27 | generated_key_values[podname] = podpath
28 | else
29 | puts "Invalid plugin specification: #{line}"
30 | end
31 | end
32 | generated_key_values
33 | end
34 |
35 | target 'Runner' do
36 | use_frameworks!
37 | use_modular_headers!
38 |
39 | # Flutter Pod
40 |
41 | copied_flutter_dir = File.join(__dir__, 'Flutter')
42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
48 |
49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
50 | unless File.exist?(generated_xcode_build_settings_path)
51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
52 | end
53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
55 |
56 | unless File.exist?(copied_framework_path)
57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
58 | end
59 | unless File.exist?(copied_podspec_path)
60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
61 | end
62 | end
63 |
64 | # Keep pod path relative so it can be checked into Podfile.lock.
65 | pod 'Flutter', :path => 'Flutter'
66 |
67 | # Plugin Pods
68 |
69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
70 | # referring to absolute paths on developers' machines.
71 | system('rm -rf .symlinks')
72 | system('mkdir -p .symlinks/plugins')
73 | plugin_pods = parse_KV_file('../.flutter-plugins')
74 | plugin_pods.each do |name, path|
75 | symlink = File.join('.symlinks', 'plugins', name)
76 | File.symlink(path, symlink)
77 | pod name, :path => File.join(symlink, 'ios')
78 | end
79 | end
80 |
81 | post_install do |installer|
82 | installer.pods_project.targets.each do |target|
83 | target.build_configurations.each do |config|
84 | config.build_settings['ENABLE_BITCODE'] = 'NO'
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/lib/screens/result_screen/result_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:flutter_spinkit/flutter_spinkit.dart';
5 | import 'package:mangamint/bloc/search_bloc/bloc.dart';
6 | import 'package:mangamint/components/my_body.dart';
7 | import 'package:mangamint/constants/base_color.dart';
8 | import 'package:mangamint/helper/color_manga_type.dart';
9 |
10 | class ResultScreen extends StatefulWidget {
11 | final String query;
12 |
13 | ResultScreen({this.query});
14 |
15 | @override
16 | _ResultScreenState createState() => _ResultScreenState();
17 | }
18 |
19 | class _ResultScreenState extends State {
20 | SearchBlocBloc _searchBlocBloc;
21 | @override
22 | void initState() {
23 | super.initState();
24 | _searchBlocBloc = BlocProvider.of(context)..add(FetchSearch(query: widget.query));
25 | }
26 | @override
27 | Widget build(BuildContext context) {
28 | ScreenUtil.init();
29 | return MyBody(
30 | showSearch: false,
31 | showRefresh: false,
32 | title: Text('Hasil Pencarian ${widget.query}',style: TextStyle(color: BaseColor.black),),
33 | body: BlocBuilder(
34 | builder: (context,state){
35 | if(state is SearchLoadingState){
36 | return Center(
37 | child: SpinKitCubeGrid(color: BaseColor.red,),
38 | );
39 | }else if (state is SearchLoadedState) {
40 | if(state.searchList.isEmpty){
41 | return Center(child: Text('Kosong Gan !!'),);
42 | }
43 | return Padding(
44 | padding: const EdgeInsets.all(8.0),
45 | child: ListView.separated(
46 | separatorBuilder: (context,index)=>Divider(color: BaseColor.grey2,),
47 | itemCount: state.searchList.length,
48 | itemBuilder: (context,i){
49 | return ListTile(
50 | onTap: (){
51 | Navigator.pushNamed(context, '/detailmanga',arguments: state.searchList[i].endpoint);
52 | },
53 | subtitle: Column(
54 | crossAxisAlignment: CrossAxisAlignment.start,
55 | children: [
56 | Text(state.searchList[i].type,style: TextStyle(
57 | color: mangaTypeColor(state.searchList[i].type)
58 | ),),
59 | Text(state.searchList[i].updated_on,style: TextStyle(
60 | color: BaseColor.grey1
61 | ),),
62 | ],
63 | ),
64 | leading: Image.network(
65 | state.searchList[i].thumb,
66 | height: MediaQuery.of(context).size.height,
67 | width: 200.w,
68 | fit: BoxFit.cover,
69 | ),
70 | title: Text(state.searchList[i].title.length >= 30 ?'${
71 | state.searchList[i].title.substring(0,30)
72 | }..':state.searchList[i].title),
73 |
74 | );
75 | },
76 | ),
77 | );
78 | }else if(state is SearchFailureState){
79 | return Padding(
80 | padding: const EdgeInsets.all(20),
81 | child: Center(child: Text(state.msg)),
82 | );
83 | }
84 | return Container();
85 | },
86 | ),
87 | );
88 | }
89 |
90 | }
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/lib/bloc/popular_bloc/popular_bloc.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:bloc/bloc.dart';
3 | import 'package:mangamint/bloc/genre_list_bloc/bloc.dart';
4 | import 'package:mangamint/models/popular_model.dart';
5 | import 'package:mangamint/repositories/popular_repo.dart';
6 | import 'package:rxdart/rxdart.dart';
7 | import './bloc.dart';
8 |
9 | class PopularBloc extends Bloc {
10 | PopularRepo _popularRepo;
11 |
12 | PopularBloc(this._popularRepo) : super(InitialPopularState());
13 |
14 | @override
15 | Stream> transformEvents(
16 | Stream events,
17 | TransitionFunction transitionFn) {
18 | return super.transformEvents(
19 | events.debounceTime(Duration(milliseconds: 500)),
20 | transitionFn
21 | );
22 | }
23 |
24 | bool _hasReachedMax(PopularState state) => state is PopularLoadedState && state.hasReachedMax;
25 | @override
26 | Stream mapEventToState(
27 | PopularEvent event,
28 | ) async* {
29 | final currentState = state;
30 | int page = 1;
31 | if(event is FetchPopular && !_hasReachedMax(currentState)){
32 | yield* _fetchPopularToState(currentState, page);
33 | }
34 | if(event is InitialFetchPopular){
35 | yield* _initialFetchToState(currentState, page);
36 | }
37 | if(event is RefreshPopular){
38 | yield* _refreshPopularToState(currentState, page);
39 | }
40 | }
41 |
42 | Stream _fetchPopularToState(PopularState currentState,int page)async*{
43 | try{
44 | // if(currentState is PopularFailureState){
45 | // yield InitialPopularState();
46 | // }
47 | if(currentState is InitialPopularState){
48 | yield PopularLoadingState();
49 | Listlist = await _popularRepo.getPopular(page: 2);
50 | yield PopularLoadedState(popularList: list,hasReachedMax: false,page: page+=1);
51 | }
52 | if(currentState is PopularLoadedState){
53 | Listlist = await _popularRepo.getPopular(page: currentState.page);
54 | yield list.isEmpty ? currentState.copyWith(hasReachedMax: true):
55 | PopularLoadedState(hasReachedMax: false,popularList: currentState.popularList+list,page: currentState.page+=1);
56 | }
57 | }catch(e){
58 | yield PopularFailureState(msg: e.toString());
59 | }
60 | }
61 |
62 | Stream _initialFetchToState(PopularState currentState,int page)async*{
63 | try{
64 | // if(currentState is PopularFailureState){
65 | // yield InitialPopularState();
66 | // }
67 | if(currentState is InitialPopularState){
68 | yield PopularLoadingState();
69 | Listlist = await _popularRepo.getPopular(page: page);
70 | yield PopularLoadedState(popularList: list,hasReachedMax: false,page: page+=1);
71 | }
72 | if(currentState is PopularLoadedState){
73 | yield PopularLoadedState(popularList: currentState.popularList,hasReachedMax: false,page:
74 | currentState.page == page ?page+1:currentState.page);
75 | }
76 | }catch(e){
77 | yield PopularFailureState(msg: e.toString());
78 | }
79 | }
80 |
81 | Stream _refreshPopularToState(PopularState currentState,int page)async*{
82 | yield PopularLoadingState();
83 | try{
84 | // if(currentState is PopularFailureState){
85 | // yield InitialPopularState();
86 | // }
87 | Listlist = await _popularRepo.getPopular(page: page);
88 | yield PopularLoadedState(popularList: list,hasReachedMax: false,page: page+=1);
89 |
90 | }catch(e){
91 | yield PopularFailureState(msg: e.toString());
92 | }
93 | }
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/lib/screens/home_screens/home_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_bloc/flutter_bloc.dart';
4 | import 'package:flutter_screenutil/flutter_screenutil.dart';
5 | import 'package:google_fonts/google_fonts.dart';
6 | import 'package:mangamint/bloc/manga_list_bloc/bloc.dart';
7 | import 'package:mangamint/bloc/genre_list_bloc/bloc.dart';
8 | import 'package:mangamint/bloc/popular_bloc/bloc.dart';
9 | import 'package:mangamint/bloc/recomended_bloc/bloc.dart';
10 | import 'package:mangamint/components/my_body.dart';
11 | import 'package:mangamint/constants/base_color.dart';
12 | import 'package:mangamint/screens/home_screens/genre_list.dart';
13 | import 'package:mangamint/screens/home_screens/my_carousel.dart';
14 | import 'package:mangamint/screens/home_screens/terbaru.dart';
15 | import 'package:mangamint/screens/home_screens/terpopular.dart';
16 |
17 | class HomeScreen extends StatefulWidget {
18 | @override
19 | _HomeScreenState createState() => _HomeScreenState();
20 | }
21 |
22 | class _HomeScreenState extends State {
23 | RecomendedBloc _recomendedBloc;
24 | GenreListBloc _genreListBloc;
25 | PopularBloc _popularBloc;
26 | MangaListBloc _mangaListBloc;
27 | @override
28 | void initState() {
29 | super.initState();
30 | init();
31 |
32 | }
33 | @override
34 | Widget build(BuildContext context) {
35 | ScreenUtil.init();
36 | return MyBody(
37 | onRefresh: (){
38 | _onRefresh();
39 | },
40 | title: Text('MangaMint',style: GoogleFonts.modak(color: BaseColor.red),),
41 | body: Padding(
42 | padding: const EdgeInsets.symmetric(horizontal: 8,vertical: 15),
43 | child: ListView(
44 | children: [
45 | MyCarousel(),
46 | // _rowTitle(
47 | // title: 'Popular',
48 | // seemore: (){
49 | // Navigator.pushNamed(context, '/popular');
50 | // },
51 | // child: TerpopularCategory()
52 | // ),
53 | _rowTitle(
54 | showMore: false,
55 | title: 'Terbaru',
56 | child: TerbaruCategory()
57 | ),
58 | _categoryTitle('Genre'),
59 | GenreListHome(),
60 | ],
61 | ),
62 | ),
63 | );
64 | }
65 | Widget _categoryTitle(String title){
66 | return Padding(
67 | padding: EdgeInsets.only(top: 10),
68 | child: Text(title,
69 | style: TextStyle(fontWeight: FontWeight.bold,fontSize: 15),),
70 | );
71 | }
72 | Widget _rowTitle({String title,Function seemore,Widget child,bool showMore = true}){
73 | return Column(
74 | children: [
75 | Row(
76 | children: [
77 | _categoryTitle(title),
78 | Spacer(),
79 | showMore?InkWell(
80 | onTap: seemore,
81 | child: Text('lihat selengkapnya')):Center(),
82 | ],
83 | ),
84 | child
85 | ],
86 | );
87 | }
88 | void init(){
89 | _recomendedBloc = BlocProvider.of(context);
90 | _recomendedBloc.add(FetchRecommended());
91 | // _popularBloc = BlocProvider.of(context);
92 | // _popularBloc.add(InitialFetchPopular());
93 | _genreListBloc = BlocProvider.of(context);
94 | _genreListBloc.add(FetchGenreList());
95 | _mangaListBloc = BlocProvider.of(context)..add(InitialFetchMangaEvent());
96 |
97 | }
98 | void _onRefresh(){
99 | _recomendedBloc.add(RefreshRecommended());
100 | // _popularBloc.add(RefreshPopular());
101 | _genreListBloc.add(RefreshGenreList());
102 | _mangaListBloc.add(RefreshMangaEvent());
103 | }
104 | }
--------------------------------------------------------------------------------
/lib/screens/tersimpan_screen/tersimpan_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:hive/hive.dart';
4 | import 'package:hive_flutter/hive_flutter.dart';
5 | import 'package:mangamint/components/my_body.dart';
6 | import 'package:mangamint/constants/base_color.dart';
7 | import 'package:mangamint/helper/color_manga_type.dart';
8 | import 'package:mangamint/helper/hive/hive_manga_model.dart';
9 | import 'package:toast/toast.dart';
10 |
11 | class TersimpanScreen extends StatefulWidget {
12 | @override
13 | _TersimpanScreenState createState() => _TersimpanScreenState();
14 | }
15 |
16 | class _TersimpanScreenState extends State {
17 | @override
18 | Widget build(BuildContext context) {
19 | return MyBody(
20 | showRefresh: false,
21 | title: Text('Tersimpan',style: TextStyle(color: BaseColor.black,fontWeight: FontWeight.bold),),
22 | body: FutureBuilder(
23 | future: Hive.openBox('manga'),
24 | builder: (context,snapshot){
25 | if(snapshot.connectionState == ConnectionState.done){
26 | if (snapshot.hasError) {
27 | return Center(child: Text('error'),);
28 | } else{
29 | return Hive.box('manga').isEmpty ?Center(
30 | child: Text('Kosong Mint xixixi'),
31 | )
32 | :_buildListview();
33 | }
34 | }
35 | return Container();
36 | },
37 | ),
38 | );
39 | }
40 | _buildListview(){
41 | var mangaBox = Hive.box('manga');
42 | return WatchBoxBuilder(
43 | box: mangaBox,
44 | builder: (context,manga) =>
45 | manga.isEmpty?Center(child: Text('kosong mint xixixi'),):ListView.builder(
46 | itemCount: mangaBox.length,
47 | itemBuilder: (context,i){
48 | HiveMangaModel mangaModel = mangaBox.getAt(i);
49 | print(mangaBox.length);
50 | return ListTile(
51 | onTap: (){
52 | Navigator.pushNamed(context, '/detailmanga',arguments:
53 | mangaModel.manga_endpoint);
54 | },
55 | title: Text(mangaModel.title.length > 20 ? '${mangaModel.title}..':mangaModel.title),
56 | subtitle: Text(mangaModel.type,style: TextStyle(color: mangaTypeColor(mangaModel.type)),),
57 | trailing: FlatButton(
58 | child: Text('Hapus',style: TextStyle(color: BaseColor.red),),
59 | shape: RoundedRectangleBorder(
60 | side: BorderSide(width: 1,color: BaseColor.red)
61 | ),
62 | onPressed: (){
63 | showDialog(context: context,
64 | builder: (nani){
65 | return AlertDialog(
66 | title: Text('Apa kamu yakin ingin menghapusnya ?'),
67 | actions: [
68 | FlatButton(
69 | child: Text('Batal'),
70 | onPressed: (){
71 | Navigator.pop(context);
72 | },
73 | ),
74 | FlatButton(
75 | child: Text('Yakin dong !!',style: TextStyle(color: BaseColor.red),),
76 | onPressed: (){
77 | mangaBox.deleteAt(i);
78 | Navigator.pop(context);
79 | Toast.show('Berhasil Dihapus', context,duration: Toast.LENGTH_LONG
80 | ,gravity: Toast.CENTER);
81 | },
82 | )
83 | ],
84 | );
85 | });
86 | },
87 | ),
88 | leading: Image.network(
89 | mangaModel.thumb,
90 | ),
91 | );
92 | },
93 | ),
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: mangamint
2 | description: A new Flutter application.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: ">=2.7.0 <3.0.0"
22 |
23 | dependencies:
24 | flutter:
25 | sdk: flutter
26 |
27 |
28 | # The following adds the Cupertino Icons font to your application.
29 | # Use with the CupertinoIcons class for iOS style icons.
30 | cupertino_icons: ^0.1.3
31 | http: ^0.12.1
32 | bloc: ^5.0.1
33 | flutter_bloc: ^5.0.1
34 | google_fonts: ^1.1.0
35 | equatable: ^1.2.0
36 | provider: ^4.3.0
37 | rxdart: ^0.24.1
38 | ff_navigation_bar: ^0.1.5
39 | flutter_spinkit: ^4.1.2+1
40 | shimmer: ^1.1.1
41 | carousel_slider: ^2.2.1
42 | flutter_screenutil: ^2.2.0
43 | cached_network_image: ^2.2.0+1
44 | photo_view: ^0.10.3
45 | shared_preferences: ^0.5.8
46 | path_provider: ^1.6.11
47 | path: ^1.7.0
48 | hive: ^1.4.1+1
49 | hive_flutter: ^0.2.1
50 | toast: ^0.1.5
51 | font_awesome_flutter: ^8.8.1
52 | url_launcher: ^5.5.0
53 | extended_image: ^0.9.0
54 | dio: ^3.0.10
55 |
56 | dev_dependencies:
57 | flutter_launcher_icons: ^0.7.5
58 | hive_generator: ^0.5.1
59 | build_runner:
60 | flutter_test:
61 | sdk: flutter
62 | flutter_icons:
63 | android: "launcher_icon"
64 | ios: true
65 | image_path: "assets/icons/icon.png"
66 |
67 | # For information on the generic Dart part of this file, see the
68 | # following page: https://dart.dev/tools/pub/pubspec
69 |
70 | # The following section is specific to Flutter.
71 | flutter:
72 |
73 | # The following line ensures that the Material Icons font is
74 | # included with your application, so that you can use the icons in
75 | # the material Icons class.
76 | uses-material-design: true
77 |
78 | # To add assets to your application, add an assets section, like this:
79 | assets:
80 | - assets/images/
81 | - assets/images/aqua.JPG
82 | # - images/a_dot_ham.jpeg
83 |
84 | # An image asset can refer to one or more resolution-specific "variants", see
85 | # https://flutter.dev/assets-and-images/#resolution-aware.
86 |
87 | # For details regarding adding assets from package dependencies, see
88 | # https://flutter.dev/assets-and-images/#from-packages
89 |
90 | # To add custom fonts to your application, add a fonts section here,
91 | # in this "flutter" section. Each entry in this list should have a
92 | # "family" key with the font family name, and a "fonts" key with a
93 | # list giving the asset and other descriptors for the font. For
94 | # example:
95 | # fonts:
96 | # - family: Schyler
97 | # fonts:
98 | # - asset: fonts/Schyler-Regular.ttf
99 | # - asset: fonts/Schyler-Italic.ttf
100 | # style: italic
101 | # - family: Trajan Pro
102 | # fonts:
103 | # - asset: fonts/TrajanPro.ttf
104 | # - asset: fonts/TrajanPro_Bold.ttf
105 | # weight: 700
106 | #
107 | # For details regarding fonts from package dependencies,
108 | # see https://flutter.dev/custom-fonts/#from-packages
109 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:flutter_bloc/flutter_bloc.dart';
4 | import 'package:google_fonts/google_fonts.dart';
5 | import 'package:hive/hive.dart';
6 | import 'package:mangamint/bloc/manga_list_bloc/bloc.dart';
7 | import 'package:mangamint/bloc/chapter_bloc/bloc.dart';
8 | import 'package:mangamint/bloc/genre_list_bloc/bloc.dart';
9 | import 'package:mangamint/bloc/manga_detail_bloc/bloc.dart';
10 | import 'package:mangamint/bloc/mangabygenre_bloc/bloc.dart';
11 | import 'package:mangamint/bloc/manhuamanhwa/bloc.dart';
12 | import 'package:mangamint/bloc/popular_bloc/bloc.dart';
13 | import 'package:mangamint/bloc/recomended_bloc/bloc.dart';
14 | import 'package:mangamint/bloc/search_bloc/bloc.dart';
15 | import 'package:mangamint/constants/base_color.dart';
16 | import 'package:mangamint/helper/hive/hive_chapter_model.dart';
17 | import 'package:mangamint/helper/hive/hive_chapter_opened_model.dart';
18 | import 'package:mangamint/helper/hive/hive_manga_model.dart';
19 | import 'package:mangamint/helper/routes.dart';
20 | import 'package:mangamint/repositories/chapter_repo.dart';
21 | import 'package:mangamint/repositories/genre_list_repo.dart';
22 | import 'package:mangamint/repositories/manga_detail_repo.dart';
23 | import 'package:mangamint/repositories/manga_list_repo.dart';
24 | import 'package:mangamint/repositories/manhua_manhwa_repo.dart';
25 | import 'package:mangamint/repositories/popular_repo.dart';
26 | import 'package:mangamint/repositories/recommended_repo.dart';
27 | import 'package:mangamint/repositories/search_repo.dart';
28 | import 'package:provider/provider.dart';
29 | import 'package:path_provider/path_provider.dart' as path_provider;
30 |
31 | void main() async{
32 | WidgetsFlutterBinding.ensureInitialized();
33 | final directory = await path_provider.getApplicationDocumentsDirectory();
34 | Hive.init(directory.path);
35 | Hive.openBox('manga');
36 | Hive.openBox('chapter');
37 | Hive.openBox('lastOpenedChapter');
38 | Hive.registerAdapter(HiveChapterModelAdapter());
39 | Hive.registerAdapter(HiveMangaModelAdapter());
40 | Hive.registerAdapter(HiveChapterOpenedModelAdapter());
41 | SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then((value) => runApp(MyApp()));
42 | }
43 |
44 | class MyApp extends StatelessWidget {
45 | @override
46 | Widget build(BuildContext context) {
47 | return MultiProvider(
48 | providers: [
49 | BlocProvider(
50 | create: (context) => MangaListBloc(MangaListRepo())),
51 | BlocProvider(
52 | create: (context) => MangaDetailBloc(MangaDetailRepo()),
53 | ),
54 | BlocProvider(
55 | create: (context) => ChapterBloc(ChapterRepo()),
56 | ),
57 | BlocProvider(
58 | create: (_) => RecomendedBloc(RecommendedRepo()),
59 | ),
60 | BlocProvider(
61 | create: (_) => GenreListBloc(GenreListRepo()),
62 | ),
63 | BlocProvider(
64 | create: (_) => PopularBloc(PopularRepo()),
65 | ),
66 | BlocProvider(
67 | create: (_) => SearchBlocBloc(SearchRepo()),
68 | ),
69 | BlocProvider(
70 | create: (_) => MangaByGenreBloc(MangaByGenreRepo()),
71 | ),
72 | BlocProvider(
73 | create: (_) => ManhuamanhwaBloc(ManhuaManhwaRepo()),
74 | ),
75 | ],
76 | child: MaterialApp(
77 | theme: ThemeData(
78 | textTheme: GoogleFonts.robotoTextTheme(
79 | Theme.of(context).textTheme
80 | ),
81 | appBarTheme: AppBarTheme(
82 | actionsIconTheme: IconThemeData(color: BaseColor.black),
83 | color: Colors.white,
84 | iconTheme: IconThemeData(color: BaseColor.red)
85 | )
86 | ),
87 | debugShowCheckedModeBanner: false,
88 | initialRoute: '/',
89 | onGenerateRoute: generateRoute,
90 | )
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/lib/screens/result_screen/terpopuler_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:mangamint/bloc/popular_bloc/bloc.dart';
5 | import 'package:mangamint/components/bottom_loader.dart';
6 | import 'package:mangamint/components/build_error.dart';
7 | import 'package:mangamint/components/item_small.dart';
8 | import 'package:mangamint/components/my_body.dart';
9 | import 'package:mangamint/components/my_shimmer.dart';
10 | import 'package:mangamint/constants/base_color.dart';
11 |
12 | class TerpopulerScreen extends StatefulWidget {
13 | TerpopulerScreen({Key key}) : super(key: key);
14 |
15 | @override
16 | _TerpopulerScreenState createState() => _TerpopulerScreenState();
17 | }
18 |
19 | class _TerpopulerScreenState extends State {
20 | PopularBloc _popularBloc;
21 | final _scrollController = ScrollController();
22 | final _scrollThreshold = 200.0;
23 | @override
24 | void initState() {
25 | super.initState();
26 | _popularBloc = BlocProvider.of(context);
27 | _scrollController.addListener(_onScroll);
28 | }
29 | @override
30 | Widget build(BuildContext context) {
31 | ScreenUtil.init();
32 | return BlocConsumer(
33 | listener: (context,state){
34 | if(state is PopularFailureState){
35 | Scaffold.of(context)..hideCurrentSnackBar()
36 | ..showSnackBar(SnackBar(
37 | content: Text(state.msg),
38 | ));
39 | }
40 | },
41 | builder: (context,state){
42 | if(state is PopularLoadingState){
43 | return MyShimmer(
44 | child: Container(
45 | height: 100,
46 | width: MediaQuery.of(context).size.width,
47 | color: BaseColor.red,
48 | ),
49 | );
50 | }else if (state is PopularLoadedState) {
51 | return MyBody(
52 | showRefresh: false,
53 | title: Text('Terpopuler',style: TextStyle(color: BaseColor.black,fontWeight: FontWeight.bold),),
54 | body: Padding(
55 | padding: const EdgeInsets.only(top: 10),
56 | child: Scrollbar(
57 | controller: _scrollController,
58 | child: GridView.builder(
59 | itemCount: state.popularList.length,
60 | shrinkWrap: true,
61 | controller: _scrollController,
62 | physics: ClampingScrollPhysics(),
63 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
64 | crossAxisCount: 3,
65 | mainAxisSpacing: 1,
66 | crossAxisSpacing: 1,
67 | childAspectRatio: 0.7
68 | ),
69 | itemBuilder: (context,i){
70 | var data = state.popularList[i];
71 | return i >= state.popularList.length?BottomLoader()
72 | :InkWell(
73 | onTap: (){
74 | Navigator.pushNamed(context, '/detailmanga',arguments:
75 | state.popularList[i].endpoint);
76 | },
77 | child: ItemSmall(
78 | title: data.title,
79 | subtitle: data.type ?? '',
80 | bottom: data.upload_on ?? '',
81 | thumb: data.thumb ?? '',
82 | onTap: (){
83 | Navigator.pushNamed(context, '/detailmanga',arguments: data.endpoint);
84 | },
85 | )
86 | );
87 | },
88 | ),
89 | ),
90 | ),
91 | );
92 | } else if (state is PopularFailureState) {
93 | return BuildError();
94 | }
95 | return Container();
96 | },
97 | );
98 | }
99 | void _onScroll() {
100 |
101 | if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
102 | _popularBloc.add(FetchPopular());
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/lib/screens/lainnya_screen/privacy.dart:
--------------------------------------------------------------------------------
1 | const String PRIVACY = '''Privacy Policy
2 | Febry Ardiansyah built the mangamint app as a Free app. This SERVICE is provided by Febry Ardiansyah at no cost and is intended for use as is.
3 |
4 | This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.
5 |
6 | If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.
7 |
8 | The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at mangamint unless otherwise defined in this Privacy Policy.
9 |
10 | Information Collection and Use
11 |
12 | For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information. The information that I request will be retained on your device and is not collected by me in any way.
13 |
14 | The app does use third party services that may collect information used to identify you.
15 |
16 | Link to privacy policy of third party service providers used by the app
17 |
18 | Google Play Services
19 | AdMob
20 | Google Analytics for Firebase
21 | Log Data
22 |
23 | I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.
24 |
25 | Cookies
26 |
27 | Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.
28 |
29 | This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.
30 |
31 | Service Providers
32 |
33 | I may employ third-party companies and individuals due to the following reasons:
34 |
35 | To facilitate our Service;
36 | To provide the Service on our behalf;
37 | To perform Service-related services; or
38 | To assist us in analyzing how our Service is used.
39 | I want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.
40 |
41 | Security
42 |
43 | I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.
44 |
45 | Links to Other Sites
46 |
47 | This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
48 |
49 | Children’s Privacy
50 |
51 | These Services do not address anyone under the age of 13. I do not knowingly collect personally identifiable information from children under 13. In the case I discover that a child under 13 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do necessary actions.
52 |
53 | Changes to This Privacy Policy
54 |
55 | I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page.
56 |
57 | This policy is effective as of 2020-07-24
58 |
59 | Contact Us
60 |
61 | If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at febryardiansyah27@gmail.com.
62 |
63 | This privacy policy page was created at privacypolicytemplate.net and modified/generated by App Privacy Policy Generator''';
--------------------------------------------------------------------------------
/lib/screens/lainnya_screen/tos.dart:
--------------------------------------------------------------------------------
1 | const String TOS = '''Terms & Conditions
2 | By downloading or using the app, these terms will automatically apply to you – you should make sure therefore that you read them carefully before using the app. You’re not allowed to copy, or modify the app, any part of the app, or our trademarks in any way. You’re not allowed to attempt to extract the source code of the app, and you also shouldn’t try to translate the app into other languages, or make derivative versions. The app itself, and all the trade marks, copyright, database rights and other intellectual property rights related to it, still belong to Febry Ardiansyah.
3 |
4 | Febry Ardiansyah is committed to ensuring that the app is as useful and efficient as possible. For that reason, we reserve the right to make changes to the app or to charge for its services, at any time and for any reason. We will never charge you for the app or its services without making it very clear to you exactly what you’re paying for.
5 |
6 | The mangamint app stores and processes personal data that you have provided to us, in order to provide my Service. It’s your responsibility to keep your phone and access to the app secure. We therefore recommend that you do not jailbreak or root your phone, which is the process of removing software restrictions and limitations imposed by the official operating system of your device. It could make your phone vulnerable to malware/viruses/malicious programs, compromise your phone’s security features and it could mean that the mangamint app won’t work properly or at all.
7 |
8 | The app does use third party services that declare their own Terms and Conditions.
9 |
10 | Link to Terms and Conditions of third party service providers used by the app
11 |
12 | Google Play Services
13 | AdMob
14 | Google Analytics for Firebase
15 | You should be aware that there are certain things that Febry Ardiansyah will not take responsibility for. Certain functions of the app will require the app to have an active internet connection. The connection can be Wi-Fi, or provided by your mobile network provider, but Febry Ardiansyah cannot take responsibility for the app not working at full functionality if you don’t have access to Wi-Fi, and you don’t have any of your data allowance left.
16 |
17 | If you’re using the app outside of an area with Wi-Fi, you should remember that your terms of the agreement with your mobile network provider will still apply. As a result, you may be charged by your mobile provider for the cost of data for the duration of the connection while accessing the app, or other third party charges. In using the app, you’re accepting responsibility for any such charges, including roaming data charges if you use the app outside of your home territory (i.e. region or country) without turning off data roaming. If you are not the bill payer for the device on which you’re using the app, please be aware that we assume that you have received permission from the bill payer for using the app.
18 |
19 | Along the same lines, Febry Ardiansyah cannot always take responsibility for the way you use the app i.e. You need to make sure that your device stays charged – if it runs out of battery and you can’t turn it on to avail the Service, Febry Ardiansyah cannot accept responsibility.
20 |
21 | With respect to Febry Ardiansyah’s responsibility for your use of the app, when you’re using the app, it’s important to bear in mind that although we endeavour to ensure that it is updated and correct at all times, we do rely on third parties to provide information to us so that we can make it available to you. Febry Ardiansyah accepts no liability for any loss, direct or indirect, you experience as a result of relying wholly on this functionality of the app.
22 |
23 | At some point, we may wish to update the app. The app is currently available on Android & iOS – the requirements for both systems(and for any additional systems we decide to extend the availability of the app to) may change, and you’ll need to download the updates if you want to keep using the app. Febry Ardiansyah does not promise that it will always update the app so that it is relevant to you and/or works with the Android & iOS version that you have installed on your device. However, you promise to always accept updates to the application when offered to you, We may also wish to stop providing the app, and may terminate use of it at any time without giving notice of termination to you. Unless we tell you otherwise, upon any termination, (a) the rights and licenses granted to you in these terms will end; (b) you must stop using the app, and (if needed) delete it from your device.
24 |
25 | Changes to This Terms and Conditions
26 |
27 | I may update our Terms and Conditions from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Terms and Conditions on this page.
28 |
29 | These terms and conditions are effective as of 2020-07-24
30 |
31 | Contact Us
32 |
33 | If you have any questions or suggestions about my Terms and Conditions, do not hesitate to contact me at febryardiansyah27@gmail.com.
34 |
35 | This Terms and Conditions page was generated by App Privacy Policy Generator''';
--------------------------------------------------------------------------------
/lib/screens/list_manga_screen/manhwa_category.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:mangamint/bloc/manhuamanhwa/bloc.dart';
5 | import 'package:mangamint/components/bottom_loader.dart';
6 | import 'package:mangamint/components/build_error.dart';
7 | import 'package:mangamint/components/my_shimmer.dart';
8 | import 'package:mangamint/constants/base_color.dart';
9 | import 'package:mangamint/helper/color_manga_type.dart';
10 |
11 | class ManhwaCategory extends StatefulWidget {
12 | ManhwaCategory({Key key}) : super(key: key);
13 |
14 | @override
15 | _ManhwaCategoryState createState() => _ManhwaCategoryState();
16 | }
17 |
18 | class _ManhwaCategoryState extends State {
19 | ManhuamanhwaBloc _manhuamanhwaBloc;
20 | final _scrollCtrl = ScrollController();
21 | final _scrollThreshold = 200.0;
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | _manhuamanhwaBloc = BlocProvider.of(context)
27 | ..add(FetchManhwa());
28 | _scrollCtrl.addListener(() {
29 | final maxScroll = _scrollCtrl.position.maxScrollExtent;
30 | final currentScroll = _scrollCtrl.position.pixels;
31 | if (maxScroll - currentScroll <= _scrollThreshold) {
32 | _manhuamanhwaBloc = BlocProvider.of(context);
33 | _manhuamanhwaBloc.add(FetchManhwa());
34 | }
35 | });
36 | }
37 | @override
38 | Widget build(BuildContext context) {
39 | ScreenUtil.init();
40 | return Padding(
41 | padding: EdgeInsets.all(8),
42 | child: BlocBuilder(
43 | builder: (context,state){
44 | if(state is ManhuaManhwaLoadingState){
45 | return MyShimmer(
46 | child: ListView.builder(
47 | itemCount: 10,
48 | physics: ClampingScrollPhysics(),
49 | shrinkWrap: true,
50 | itemBuilder: (context,i){
51 | return ListTile(
52 | leading: Container(
53 | height: 100.h,
54 | width: 200.w,
55 | color: BaseColor.red,
56 | ),
57 | title: Container(
58 | height: 100.h,
59 | width:MediaQuery.of(context).size.width,
60 | color: BaseColor.red,
61 | ),
62 | );
63 | },
64 | ),
65 | );
66 | }else if(state is ManhwaLoadedState){
67 | return Scrollbar(
68 | child: ListView.separated(
69 | separatorBuilder: (context,index)=>Divider(color: BaseColor.grey2,),
70 | itemCount: state.hasReachedMax
71 | ? state.list.length
72 | : state.list.length + 1,
73 | controller: _scrollCtrl,
74 | itemBuilder: (context, i) {
75 | return i >= state.list.length
76 | ? BottomLoader()
77 | : ListTile(
78 | onTap: (){
79 | Navigator.pushNamed(context, '/detailmanga',arguments:
80 | state.list[i].endpoint);
81 | },
82 | title: Text(state.list[i].title.length > 20
83 | ? '${state.list[i].title.substring(0, 20)}..'
84 | : state.list[i].title),
85 | subtitle: Column(
86 | crossAxisAlignment: CrossAxisAlignment.start,
87 | children: [
88 | Text(state.list[i].type,style: TextStyle(
89 | color: mangaTypeColor(state.list[i].type)
90 | ),),
91 | Text(state.list[i].updated_on,style: TextStyle(
92 | color:BaseColor.grey1
93 | ),),
94 | ],
95 | ),
96 | leading: Image.network(
97 | state.list[i].thumb,
98 | height: MediaQuery.of(context).size.height,
99 | width: 200.w,
100 | fit: BoxFit.cover,
101 | ),
102 | trailing: SizedBox(
103 | height: 100.h,
104 | width: 200.w,
105 | child: Text(
106 | state.list[i].chapter,
107 | style: TextStyle(
108 | fontWeight: FontWeight.bold,
109 | ),
110 | ),
111 | ),
112 | );
113 | },
114 | ),
115 | );
116 | }else if(state is ManhuaManhwaFailureState){
117 | return BuildError();
118 | }
119 | return Container();
120 | },
121 | ),
122 | );
123 | }
124 | }
--------------------------------------------------------------------------------
/lib/screens/list_manga_screen/semuanya_category.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_bloc/flutter_bloc.dart';
4 | import 'package:flutter_screenutil/flutter_screenutil.dart';
5 | import 'package:mangamint/bloc/manga_list_bloc/bloc.dart';
6 | import 'package:mangamint/components/bottom_loader.dart';
7 | import 'package:mangamint/components/build_error.dart';
8 | import 'package:mangamint/components/my_shimmer.dart';
9 | import 'package:mangamint/constants/base_color.dart';
10 | import 'package:mangamint/helper/color_manga_type.dart';
11 |
12 | class SemuanyaCategory extends StatefulWidget {
13 | @override
14 | _SemuanyaCategoryState createState() => _SemuanyaCategoryState();
15 | }
16 |
17 | class _SemuanyaCategoryState extends State {
18 | final _scrollCtrl = ScrollController();
19 | final _scrollThreshold = 200.0;
20 | MangaListBloc _mangaListBloc;
21 |
22 | @override
23 | void initState() {
24 | super.initState();
25 | _scrollCtrl.addListener(() {
26 | final maxScroll = _scrollCtrl.position.maxScrollExtent;
27 | final currentScroll = _scrollCtrl.position.pixels;
28 | if (maxScroll - currentScroll <= _scrollThreshold) {
29 | _mangaListBloc = BlocProvider.of(context);
30 | _mangaListBloc.add(FetchManga());
31 | }
32 | });
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | ScreenUtil.init();
38 | return Padding(
39 | padding: const EdgeInsets.all(8.0),
40 | child: BlocBuilder(
41 | builder: (context, state) {
42 | if (state is MangaListLoadingState) {
43 | return MyShimmer(
44 | child: ListView.builder(
45 | itemCount: 10,
46 | physics: ClampingScrollPhysics(),
47 | shrinkWrap: true,
48 | itemBuilder: (context, i) {
49 | return ListTile(
50 | leading: Container(
51 | height: 100.h,
52 | width: 200.w,
53 | color: BaseColor.red,
54 | ),
55 | title: Container(
56 | height: 100.h,
57 | width: MediaQuery.of(context).size.width,
58 | color: BaseColor.red,
59 | ),
60 | );
61 | },
62 | ),
63 | );
64 | } else if (state is MangaListStateLoaded) {
65 | return Scrollbar(
66 | child: ListView.separated(
67 | separatorBuilder: (context,index)=>Divider(color: BaseColor.grey2,),
68 | itemCount: state.hasReachedMax
69 | ? state.mangaList.length
70 | : state.mangaList.length + 1,
71 | controller: _scrollCtrl,
72 | itemBuilder: (context, i) {
73 | return i >= state.mangaList.length
74 | ? BottomLoader()
75 | : ListTile(
76 | onTap: () {
77 | Navigator.pushNamed(context, '/detailmanga',
78 | arguments: state.mangaList[i].endpoint);
79 | },
80 | title: Text(state.mangaList[i].title.length > 20
81 | ? '${state.mangaList[i].title.substring(0, 20)}..'
82 | : state.mangaList[i].title),
83 | subtitle: Column(
84 | crossAxisAlignment: CrossAxisAlignment.start,
85 | children: [
86 | Text(
87 | state.mangaList[i].type,
88 | style: TextStyle(
89 | color: mangaTypeColor(state.mangaList[i].type)),
90 | ),
91 | Text(
92 | state.mangaList[i].updated_on,
93 | style: TextStyle(
94 | color: BaseColor.grey1),
95 | ),
96 | ],
97 | ),
98 | leading: Image.network(
99 | state.mangaList[i].thumb,
100 | height: MediaQuery.of(context).size.height,
101 | width: 200.w,
102 | fit: BoxFit.cover,
103 | ),
104 | trailing: SizedBox(
105 | height: 100.h,
106 | width: 200.w,
107 | child: Text(
108 | state.mangaList[i].chapter,
109 | style: TextStyle(
110 | fontWeight: FontWeight.bold,
111 | ),
112 | ),
113 | ),
114 | );
115 | },
116 | ),
117 | );
118 | }else if(state is MangaListStateFailure){
119 | return BuildError();
120 | }
121 | return Container();
122 | },
123 | ),
124 | );
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/lib/screens/result_screen/manga_by_genre_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:mangamint/bloc/mangabygenre_bloc/bloc.dart';
5 | import 'package:mangamint/components/bottom_loader.dart';
6 | import 'package:mangamint/components/my_body.dart';
7 | import 'package:mangamint/components/my_shimmer.dart';
8 | import 'package:mangamint/constants/base_color.dart';
9 | import 'package:mangamint/helper/color_manga_type.dart';
10 |
11 | class MangaByGenreScreen extends StatefulWidget {
12 | final String endpoint;
13 |
14 | MangaByGenreScreen({Key key, this.endpoint}) : super(key: key);
15 |
16 | @override
17 | _MangaByGenreScreenState createState() => _MangaByGenreScreenState();
18 | }
19 |
20 | class _MangaByGenreScreenState extends State {
21 | MangaByGenreBloc _mangaByGenreBloc;
22 |
23 | final _scrollController = ScrollController();
24 | final _scrollThreshold = 200.0;
25 |
26 | void _onScroll() {
27 |
28 | if(_scrollController.position.pixels == _scrollController.position.maxScrollExtent){
29 | _mangaByGenreBloc.add(FetchMangByGenre(endpoint: widget.endpoint));
30 | }
31 | // final maxScroll = _scrollController.position.maxScrollExtent;
32 | // final currentScroll = _scrollController.position.pixels;
33 | // if (maxScroll - currentScroll <= _scrollThreshold) {
34 | // _mangaByGenreBloc.add(FetchMangByGenre(endpoint: widget.endpoint));
35 | // }
36 | }
37 | @override
38 | void initState() {
39 | super.initState();
40 | _mangaByGenreBloc = BlocProvider.of(context)
41 | ..add(InitialMangaByGenreEvent(endpoint: widget.endpoint));
42 | _scrollController.addListener(_onScroll);
43 | }
44 |
45 | @override
46 | Widget build(BuildContext context) {
47 | ScreenUtil.init();
48 | return MyBody(
49 | onSearch: (){
50 | Navigator.pushNamed(context, '/search');
51 | },
52 | onRefresh: (){
53 | _mangaByGenreBloc = BlocProvider.of(context)
54 | ..add(InitialMangaByGenreEvent(endpoint: widget.endpoint));
55 | },
56 | title: Text(
57 | '${widget.endpoint[0].toUpperCase()}${widget.endpoint.substring(1, widget.endpoint.length - 1)}',
58 | style: TextStyle(color: BaseColor.black),
59 | ),
60 | body: BlocConsumer(
61 | listener: (context,state){
62 | if(state is MangaByGenreFailureState){
63 | Scaffold.of(context)..hideCurrentSnackBar()
64 | ..showSnackBar(SnackBar(
65 | content: Text('Njir bruh, gk ada paketan kah ?'),
66 | ));
67 | }
68 |
69 | },
70 | builder: (context,state){
71 | print(state);
72 | if(state is MangaByGenreLoadingState || state is MangaByGenreFailureState){
73 | return MyShimmer(
74 | child: ListView.builder(
75 | itemCount: 10,
76 | physics: ClampingScrollPhysics(),
77 | shrinkWrap: true,
78 | itemBuilder: (context,i){
79 | return ListTile(
80 | leading: Container(
81 | height: 100.h,
82 | width: 200.w,
83 | color: BaseColor.red,
84 | ),
85 | title: Container(
86 | height: 100.h,
87 | width:MediaQuery.of(context).size.width,
88 | color: BaseColor.red,
89 | ),
90 | );
91 | },
92 | ),
93 | );
94 | }
95 |
96 | else if(state is MangaByGenreLoadedState){
97 |
98 | if(state.list.isEmpty){
99 | return Text('no manga');
100 | }
101 | return ListView.builder(
102 | padding: const EdgeInsets.all(8.0),
103 | controller: _scrollController,
104 | itemCount : state.hasReachedMax? state.list.length:state.list.length+1,
105 | itemBuilder: (context,i){
106 | // print('list ${state.list.length}');
107 | // print('index $i');
108 | print('max ? ${state.hasReachedMax}');
109 | return i >= state.list.length?BottomLoader(): ListTile(
110 | onTap: (){
111 | Navigator.pushNamed(context, '/detailmanga',arguments: state.list[i].endpoint);
112 | },
113 | subtitle: Text(state.list[i].type,style: TextStyle(
114 | color: mangaTypeColor(state.list[i].type)
115 | ),),
116 | leading: Image.network(
117 | state.list[i].thumb ?? 'https://webhostingmedia.net/wp-content/uploads/2018/01/http-error-404-not-found.png',
118 | height: MediaQuery.of(context).size.height,
119 | width: 200.w,
120 | fit: BoxFit.cover,
121 | ),
122 | title: Text(state.list[i].title.length >= 30 ?'${
123 | state.list[i].title.substring(0,30)
124 | }..':state.list[i].title),
125 | );
126 | },
127 | );
128 | }
129 | return Container();
130 | },
131 | ),
132 | );
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/lib/screens/list_manga_screen/manhua_category.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:mangamint/bloc/manga_detail_bloc/bloc.dart';
5 | import 'package:mangamint/bloc/manhuamanhwa/bloc.dart';
6 | import 'package:mangamint/components/bottom_loader.dart';
7 | import 'package:mangamint/components/build_error.dart';
8 | import 'package:mangamint/components/loading_dialog.dart';
9 | import 'package:mangamint/components/my_shimmer.dart';
10 | import 'package:mangamint/constants/base_color.dart';
11 | import 'package:flutter/material.dart';
12 | import 'package:mangamint/helper/color_manga_type.dart';
13 |
14 | class ManhuaCategory extends StatefulWidget {
15 | @override
16 | _ManhuaCategoryState createState() => _ManhuaCategoryState();
17 | }
18 |
19 | class _ManhuaCategoryState extends State {
20 | ManhuamanhwaBloc _manhuamanhwaBloc;
21 | final _scrollCtrl = ScrollController();
22 | final _scrollThreshold = 200.0;
23 |
24 | @override
25 | void initState() {
26 | super.initState();
27 | _manhuamanhwaBloc = BlocProvider.of(context)
28 | ..add(FetchManhua(endpoint: 'manhua'));
29 |
30 | _scrollCtrl.addListener(() {
31 | final maxScroll = _scrollCtrl.position.maxScrollExtent;
32 | final currentScroll = _scrollCtrl.position.pixels;
33 | if (maxScroll - currentScroll <= _scrollThreshold) {
34 | _manhuamanhwaBloc = BlocProvider.of(context);
35 | _manhuamanhwaBloc.add(FetchManhua(endpoint: 'manhua'));
36 | }
37 | });
38 | }
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | ScreenUtil.init();
43 | return Padding(
44 | padding: EdgeInsets.all(8),
45 | child: BlocBuilder(
46 | builder: (context, state) {
47 | if (state is ManhuaManhwaLoadingState) {
48 | return MyShimmer(
49 | child: ListView.builder(
50 | itemCount: 10,
51 | physics: ClampingScrollPhysics(),
52 | shrinkWrap: true,
53 | itemBuilder: (context, i) {
54 | return ListTile(
55 | leading: Container(
56 | height: 100.h,
57 | width: 200.w,
58 | color: BaseColor.red,
59 | ),
60 | title: Container(
61 | height: 100.h,
62 | width: MediaQuery.of(context).size.width,
63 | color: BaseColor.red,
64 | ),
65 | );
66 | },
67 | ),
68 | );
69 | } else if (state is ManhuaLoadedState) {
70 | return Scrollbar(
71 | child: ListView.separated(
72 | separatorBuilder: (context, index) => Divider(
73 | color: BaseColor.grey2,
74 | ),
75 | itemCount: state.hasReachedMax
76 | ? state.list.length
77 | : state.list.length + 1,
78 | controller: _scrollCtrl,
79 | itemBuilder: (context, i) {
80 | return i >= state.list.length
81 | ? BottomLoader()
82 | : ListTile(
83 | onTap: () {
84 | Navigator.pushNamed(context, '/detailmanga',
85 | arguments: state.list[i].endpoint);
86 | },
87 | title: Text(state.list[i].title.length > 20
88 | ? '${state.list[i].title.substring(0, 20)}..'
89 | : state.list[i].title),
90 | subtitle: Column(
91 | crossAxisAlignment: CrossAxisAlignment.start,
92 | children: [
93 | Text(
94 | state.list[i].type,
95 | style: TextStyle(
96 | color: mangaTypeColor(state.list[i].type)),
97 | ),
98 | Text(state.list[i].updated_on,
99 | style: TextStyle(color: BaseColor.grey1)),
100 | ],
101 | ),
102 | trailing: SizedBox(
103 | height: 100.h,
104 | width: 200.w,
105 | child: Text(
106 | state.list[i].chapter,
107 | style: TextStyle(
108 | fontWeight: FontWeight.bold,
109 | ),
110 | ),
111 | ),
112 | leading: Image.network(
113 | state.list[i].thumb,
114 | height: MediaQuery.of(context).size.height,
115 | width: 200.w,
116 | fit: BoxFit.cover,
117 | ),
118 | );
119 | },
120 | ),
121 | );
122 | } else if (state is ManhuaManhwaFailureState) {
123 | return BuildError();
124 | }
125 | return Container();
126 | },
127 | ),
128 | );
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/lib/screens/home_screens/my_carousel.dart:
--------------------------------------------------------------------------------
1 | import 'package:carousel_slider/carousel_slider.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_bloc/flutter_bloc.dart';
5 | import 'package:flutter_screenutil/flutter_screenutil.dart';
6 | import 'package:mangamint/bloc/recomended_bloc/bloc.dart';
7 | import 'package:mangamint/components/image_cache_loading.dart';
8 | import 'package:mangamint/components/my_shimmer.dart';
9 | import 'package:mangamint/constants/base_color.dart';
10 |
11 | class MyCarousel extends StatefulWidget {
12 | @override
13 | _MyCarouselState createState() => _MyCarouselState();
14 | }
15 |
16 | class _MyCarouselState extends State {
17 | int _current = 0;
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | ScreenUtil.init();
22 | return BlocConsumer(
23 | listener: (context,state){
24 | if(state is RecomendedFailureState){
25 | Scaffold.of(context)..hideCurrentSnackBar()
26 | ..showSnackBar(SnackBar(
27 | content: Row(
28 | children: [
29 | Icon(Icons.info_outline),
30 | Text('Kenapa hayoo, cobak cek internet mu cuk !!',style: TextStyle(color: Colors.white),),
31 | ],
32 | ),
33 | backgroundColor: BaseColor.green,
34 | ));
35 | }
36 | },
37 | builder: (context,state){
38 | if (state is RecommendedLoadingState || state is RecomendedFailureState) {
39 | return MyShimmer(
40 | child: ClipRRect(
41 | borderRadius: BorderRadius.all(Radius.circular(8)),
42 | child: Container(
43 | height: 500.h,
44 | width: MediaQuery.of(context).size.width,
45 | color: BaseColor.red,
46 | ),
47 | ),
48 | );
49 | }else if (state is RecommendedLoadedState) {
50 | return Column(
51 | children: [
52 | CarouselSlider(
53 | items: state.recommendedList.map((e){
54 | return InkWell(
55 | onTap: (){
56 | Navigator.pushNamed(context, '/detailmanga',arguments:
57 | e.endpoint);
58 | },
59 | child: ClipRRect(
60 | borderRadius: BorderRadius.all(Radius.circular(8)),
61 | child: ImageCacheLoading(
62 | imgUrl: e.thumb,
63 | imageBuilder: (context,imgProvider){
64 | return Container(
65 | width: MediaQuery.of(context).size.width,
66 | height: 500.h,
67 | decoration: BoxDecoration(
68 | image: DecorationImage(
69 | image: imgProvider,
70 | fit: BoxFit.cover
71 | )
72 | ),
73 | child: Container(
74 | padding: EdgeInsets.all(8),
75 | child: Center(child: Text(e.title,style: TextStyle(color: Colors.white,fontWeight: FontWeight.bold,fontSize: 20),)),
76 | width: MediaQuery.of(context).size.width,
77 | decoration: BoxDecoration(
78 | gradient: LinearGradient(
79 | begin: Alignment.bottomCenter,
80 | end: Alignment.center,
81 | colors: [
82 | BaseColor.black,
83 | Colors.black.withOpacity(0.4)
84 | ]
85 | )
86 | ),
87 | ),
88 | );
89 | },
90 | ),
91 | ),
92 | );
93 | }).toList(),
94 | options: CarouselOptions(
95 | autoPlay: true,
96 | enlargeCenterPage: true,
97 | aspectRatio: 2.0,
98 | onPageChanged: (index, reason) {
99 | setState(() {
100 | _current = index;
101 | });
102 | }
103 | ),
104 | ),
105 | Row(
106 | mainAxisAlignment: MainAxisAlignment.start,
107 | children: state.recommendedList.map((url) {
108 | int index = state.recommendedList.indexOf(url);
109 | return Container(
110 | width: 8.0,
111 | height: 8.0,
112 | margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 2.0),
113 | decoration: BoxDecoration(
114 | shape: BoxShape.circle,
115 | color: _current == index
116 | ? Color.fromRGBO(0, 0, 0, 0.9)
117 | : Color.fromRGBO(0, 0, 0, 0.4),
118 | ),
119 | );
120 | }).toList(),
121 | ),
122 | ],
123 | );
124 | }
125 | // if(state is RecomendedFailureState){
126 | // print(state.msg);
127 | // return Text('Cek internet mu euy atau tunggu nanti');
128 | // }
129 | return Container();
130 | },
131 | );
132 | }
133 | }
134 |
--------------------------------------------------------------------------------