Skip to content

検索画面 #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/api_client/api_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@ abstract class ApiClient {
@Query("category") String category,
@Query("apiKey") String apiKey,
);

@GET("/everything")
Future<News> fetchSearchNews(
@Query("q") String query,
@Query("apiKey") String apiKey,
);
}
40 changes: 1 addition & 39 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,4 @@ class NewsApiApp extends StatelessWidget {
home: const HomePage(title: 'News API'),
);
}
}

class NewsApiPage extends StatefulWidget {
const NewsApiPage({Key? key, required this.title}) : super(key: key);

// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.

// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".

final String title;

@override
State<NewsApiPage> createState() => _NewsApiPageState();
}

class _NewsApiPageState extends State<NewsApiPage> {
int _counter = 0;

void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}

@override
Widget build(BuildContext context) {
return const NewsListPage();
}
}
}
24 changes: 19 additions & 5 deletions lib/repository/articles_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,33 @@ import '../../model/news.dart';
import '../ui/response/result.dart';

abstract class ArticlesRepository {
Future<Result<News>> fetchHeadlines({required String country, required String category});
Future<Result<News>> fetchHeadlines(
{required String country, required String category});
Future<Result<News>> fetchSearchNews(
{required String query});
}

class ArticlesRepositoryImpl extends ArticlesRepository {
final ApiClient _client;

ArticlesRepositoryImpl([ApiClient? client]): _client = client ?? ApiClient(Dio());
ArticlesRepositoryImpl([ApiClient? client])
: _client = client ?? ApiClient(Dio());

@override
Future<Result<News>> fetchHeadlines({required String country, required String category}) {
Future<Result<News>> fetchHeadlines(
{required String country, required String category}) {
return _client
.fetchHeadlines(country, category, EnvironemntVariables.newsApiKey)
.then((news) => Result<News>.success(news))
.catchError((error)=>Result<News>.failure(error));
.catchError((error) => Result<News>.failure(error));
}
}

@override
Future<Result<News>> fetchSearchNews(
{required String query}) {
return _client
.fetchSearchNews(query, EnvironemntVariables.newsApiKey)
.then((news) => Result<News>.success(news))
.catchError((error) => Result<News>.failure(error));
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_news_api/repository/articles_repository.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../model/news.dart';
Expand All @@ -9,13 +10,15 @@ final articlesNotifierProvider = StateNotifierProvider<ArticlesNotifier, List<Ar
class ArticlesNotifier extends StateNotifier<List<Article>> {
ArticlesNotifier(): super([]);

void fetchHeadlines() async {
void fetch() async {
final ArticlesRepository repository = ArticlesRepositoryImpl();
await repository.fetchHeadlines(country: "us", category: "business").then((result) {
result.when(success: (news) {
state = news.articles;
}, failure: (error) {
print(error.message);
if (kDebugMode) {
print(error.message);
}
});
});
}
Expand Down
6 changes: 6 additions & 0 deletions lib/state/news_search_text_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

final newsSearchTextProvider = StateProvider((ref) {
return TextEditingController(text: '');
});
28 changes: 28 additions & 0 deletions lib/state/search_articles.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_news_api/repository/articles_repository.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../model/news.dart';

final searchArticlesNotifierProvider = StateNotifierProvider<SearchArticlesNotifier, List<Article>>((ref) {
return SearchArticlesNotifier();
});

class SearchArticlesNotifier extends StateNotifier<List<Article>> {
SearchArticlesNotifier(): super([]);

void search({
required String query
}) async {
final ArticlesRepository repository = ArticlesRepositoryImpl();
await repository.fetchSearchNews(query: query).then((result) {
result.when(success: (news) {
state = news.articles;
}, failure: (error) {
if (kDebugMode) {
print(error.message);
}
});
});
}
}

37 changes: 37 additions & 0 deletions lib/ui/components/articles_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_news_api/model/news.dart';

import 'articles_list_item.dart';

class ArticlesList extends StatelessWidget {
const ArticlesList({
Key? key,
required this.articles,
required this.onRefresh,
}) : super(key: key);

final List<Article> articles;
final Function onRefresh;

@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
await Future.delayed(const Duration(seconds: 0), () {
onRefresh();
});
},
child: Center(
child: ListView.builder(
shrinkWrap: true,
// physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.only(top: 5),
itemCount: articles.length,
itemBuilder: (context, index) {
return ArticleListItem(article: articles[index]);
}),
),
);
}
}
71 changes: 71 additions & 0 deletions lib/ui/components/articles_list_item.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_news_api/model/news.dart';
import 'package:flutter_news_api/ui/webview/custom_webview.dart';
import 'package:timeago/timeago.dart' as timeago;

class ArticleListItem extends StatelessWidget {
const ArticleListItem({
Key? key,
required this.article,
}) : super(key: key);

final Article article;

@override
Widget build(BuildContext context) {
timeago.setLocaleMessages('ja', timeago.JaMessages());
final now = DateTime.now();
final ago = now.difference(article.publishedAt);
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return CustomWebview(url: article.url);
}));
},
child: Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(width: 0.3, color: Colors.grey))),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
height: 100,
child: Row(
children: [
Image.network(
article.urlToImage ??
"https://www.shoshinsha-design.com/wp-content/uploads/2020/05/noimage-760x460.png",
width: 70,
height: 70,
),

Flexible(
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
timeago.format(now.subtract(ago), locale: 'ja'),
style: const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
),
),
Text(
article.title ?? "No title",
style: const TextStyle(
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.clip,
maxLines: 2,
),
],
),
),
),
// )
],
),
),
);
}
}
100 changes: 14 additions & 86 deletions lib/ui/news_list/news_list_page.dart
Original file line number Diff line number Diff line change
@@ -1,77 +1,11 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_news_api/ui/webview/custom_webview.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_news_api/model/news.dart';
import 'package:flutter_news_api/state/articles.dart';
import 'package:flutter/material.dart';
import 'package:flutter_news_api/ui/components/articles_list.dart';
import 'package:flutter_news_api/ui/components/articles_list_item.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../model/news.dart';
import '../../provider/articles_notifier_provider.dart';
import '../../state/articles_notifier_provider.dart';

class ArticleListItem extends StatelessWidget {
const ArticleListItem({
Key? key,
required this.article,
}) : super(key: key);

final Article article;

@override
Widget build(BuildContext context) {
timeago.setLocaleMessages('ja', timeago.JaMessages());
final now = DateTime.now();
final ago = now.difference(article.publishedAt);
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return CustomWebview(url: article.url);
}));
},
child: Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(width: 0.3, color: Colors.grey))),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
height: 100,
child: Row(
children: [
Image.network(
article.urlToImage ??
"https://www.shoshinsha-design.com/wp-content/uploads/2020/05/noimage-760x460.png",
width: 70,
height: 70,
),

Flexible(
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
timeago.format(now.subtract(ago), locale: 'ja'),
style: const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
),
),
Text(
article.title ?? "No title",
style: const TextStyle(
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.clip,
maxLines: 2,
),
],
),
),
),
// )
],
),
),
);
}
}

class NewsListPage extends HookConsumerWidget {
const NewsListPage({
Expand All @@ -81,22 +15,16 @@ class NewsListPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final List<Article> articles = ref.watch(articlesNotifierProvider);
ref.read(articlesNotifierProvider.notifier).fetchHeadlines();

return RefreshIndicator(
onRefresh: () async {
await Future.delayed(const Duration(seconds: 0), () {
ref.read(articlesNotifierProvider.notifier).fetchHeadlines();
});
},
child: Center(
child: ListView.builder(
padding: const EdgeInsets.only(top: 5),
itemCount: articles.length,
itemBuilder: (context, index) {
return ArticleListItem(article: articles[index]);
}),
),
useMemoized(() => {
ref.read(articlesNotifierProvider.notifier).fetch()
});

return ArticlesList(
articles: articles,
onRefresh: () {
ref.read(articlesNotifierProvider.notifier).fetch();
},
);
}
}
Loading