-
Notifications
You must be signed in to change notification settings - Fork 0
Contributors表示 #400
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
base: main
Are you sure you want to change the base?
Contributors表示 #400
Changes from all commits
5d632e9
6a9edae
1abd01a
7109ab7
19d8693
1904bd4
861fd29
320cac9
9f07068
7507005
a2d8ae7
200be56
dc008b5
90cdf50
4bd0e81
1c27adb
c401584
17a17c9
88a2b70
7c4d580
b476cad
163063a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||
|
|
||
| part 'github_profile.freezed.dart'; | ||
| part 'github_profile.g.dart'; | ||
|
|
||
| @freezed | ||
| abstract class GitHubProfile with _$GitHubProfile { | ||
| @JsonSerializable(fieldRename: FieldRename.snake) | ||
| const factory GitHubProfile({ | ||
| @JsonKey(fromJson: _idFromJson) required String id, | ||
| required String login, | ||
| required String avatarUrl, | ||
| required String htmlUrl, | ||
| }) = _GitHubProfile; | ||
|
|
||
| factory GitHubProfile.fromJson(Map<String, Object?> json) => | ||
| _$GitHubProfileFromJson(json); | ||
| } | ||
|
|
||
| String _idFromJson(Object? json) => json == null ? '' : json.toString(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import 'package:dotto/domain/github_profile.dart'; | ||
| import 'package:dotto/feature/github_contributor/github_contributor_viewmodel.dart'; | ||
| import 'package:dotto/feature/github_contributor/github_contributor_viewstate.dart'; | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
| import 'package:url_launcher/url_launcher_string.dart'; | ||
|
|
||
| final class GitHubContributorScreen extends ConsumerWidget { | ||
| const GitHubContributorScreen({super.key}); | ||
|
|
||
| Widget _githubContributorListRow(GitHubProfile githubProfile) { | ||
| return ListTile( | ||
| leading: GestureDetector( | ||
| child: CircleAvatar( | ||
| radius: 20, | ||
| backgroundImage: NetworkImage(githubProfile.avatarUrl), | ||
| backgroundColor: Colors.grey.shade200, | ||
| ), | ||
| ), | ||
| title: Text(githubProfile.login), | ||
| onTap: () => launchUrlString(githubProfile.htmlUrl), | ||
| ); | ||
| } | ||
|
|
||
| Widget _body( | ||
| AsyncValue<GitHubContributorViewState> viewModelAsync, { | ||
| required Future<void> Function() onRefresh, | ||
| }) { | ||
| switch (viewModelAsync) { | ||
| case AsyncData(:final value): | ||
| return RefreshIndicator( | ||
| onRefresh: onRefresh, | ||
| child: ListView.separated( | ||
| itemCount: value.contributors.length, | ||
| separatorBuilder: (_, _) => const Divider(height: 0), | ||
| itemBuilder: (_, index) { | ||
| final contributor = value.contributors[index]; | ||
| return _githubContributorListRow(contributor); | ||
| }, | ||
| ), | ||
| ); | ||
| case AsyncError(:final error): | ||
| return Center(child: Text('エラーが発生しました: $error')); | ||
| case AsyncLoading(): | ||
| return const Center(child: CircularProgressIndicator()); | ||
| } | ||
| } | ||
|
|
||
| @override | ||
| Widget build(BuildContext context, WidgetRef ref) { | ||
| final viewModelAsync = ref.watch(gitHubContributorViewModelProvider); | ||
|
|
||
| return Scaffold( | ||
| appBar: AppBar(title: const Text('開発者一覧')), | ||
| body: _body( | ||
| viewModelAsync, | ||
| onRefresh: () async { | ||
| await ref | ||
| .read(gitHubContributorViewModelProvider.notifier) | ||
| .onRefresh(); | ||
| }, | ||
| ), | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import 'package:dotto/domain/github_profile.dart'; | ||
| import 'package:dotto/repository/github_contoributor_repository.dart'; | ||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
|
|
||
| final class GitHubContributorService { | ||
| GitHubContributorService(this.ref); | ||
|
|
||
| final Ref ref; | ||
|
|
||
| Future<List<GitHubProfile>> getContributors() async { | ||
| return ref.read(githubContributionRepositoryProvider).getContributors(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import 'package:dotto/feature/github_contributor/github_contributor_service.dart'; | ||
| import 'package:dotto/feature/github_contributor/github_contributor_viewstate.dart'; | ||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||
|
|
||
| part 'github_contributor_viewmodel.g.dart'; | ||
|
|
||
| @riverpod | ||
| final class GitHubContributorViewModel extends _$GitHubContributorViewModel { | ||
| late final GitHubContributorService _service; | ||
|
|
||
| @override | ||
| Future<GitHubContributorViewState> build() async { | ||
| _service = GitHubContributorService(ref); | ||
| final contributors = await _service.getContributors(); | ||
| return GitHubContributorViewState(contributors: contributors); | ||
| } | ||
|
|
||
| Future<void> onRefresh() async { | ||
| state = await AsyncValue.guard(() async { | ||
| final contributors = await _service.getContributors(); | ||
| return GitHubContributorViewState(contributors: contributors); | ||
| }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import 'package:dotto/domain/github_profile.dart'; | ||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||
|
|
||
| part 'github_contributor_viewstate.freezed.dart'; | ||
|
|
||
| @freezed | ||
| abstract class GitHubContributorViewState with _$GitHubContributorViewState { | ||
| const factory GitHubContributorViewState({ | ||
| required List<GitHubProfile> contributors, | ||
| }) = _GitHubContributorViewState; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||||||||||||||||||||||
| import 'package:dotto/domain/github_profile.dart'; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| import 'package:dio/dio.dart'; | ||||||||||||||||||||||||||||||
| import 'package:flutter/foundation.dart'; | ||||||||||||||||||||||||||||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| final githubContributionRepositoryProvider = | ||||||||||||||||||||||||||||||
| Provider<GitHubContributorRepository>( | ||||||||||||||||||||||||||||||
| (ref) => GitHubContributorRepositoryImpl(ref), | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| abstract class GitHubContributorRepository { | ||||||||||||||||||||||||||||||
| Future<List<GitHubProfile>> getContributors(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| final class GitHubContributorRepositoryImpl | ||||||||||||||||||||||||||||||
| implements GitHubContributorRepository { | ||||||||||||||||||||||||||||||
| GitHubContributorRepositoryImpl(this.ref); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| final Ref ref; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||||
| Future<List<GitHubProfile>> getContributors() async { | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| final dio = Dio(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| // GitHub contributors API for this repository | ||||||||||||||||||||||||||||||
| const url = 'https://api.github.com/repos/fun-dotto/dotto/contributors'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| final response = await dio.get(url); | ||||||||||||||||||||||||||||||
| if (response.statusCode != 200) { | ||||||||||||||||||||||||||||||
| throw Exception('Failed to get contributors'); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| final data = response.data; | ||||||||||||||||||||||||||||||
| if (data == null || data is! List) { | ||||||||||||||||||||||||||||||
| throw Exception('Failed to get contributors'); | ||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+35
|
||||||||||||||||||||||||||||||
| throw Exception('Failed to get contributors'); | |
| } | |
| final data = response.data; | |
| if (data == null || data is! List) { | |
| throw Exception('Failed to get contributors'); | |
| throw Exception( | |
| 'Failed to get contributors: HTTP ${response.statusCode}', | |
| ); | |
| } | |
| final data = response.data; | |
| if (data == null || data is! List) { | |
| throw Exception('Failed to get contributors: invalid response format'); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,98 @@ | ||||||
| import 'package:dotto/domain/github_profile.dart'; | ||||||
| import 'package:dotto/feature/github_contributor/github_contributor_service.dart'; | ||||||
| import 'package:dotto/repository/github_contoributor_repository.dart'; | ||||||
|
||||||
| import 'package:dotto/repository/github_contoributor_repository.dart'; | |
| import 'package:dotto/repository/github_contributor_repository.dart'; |
Copilot
AI
Dec 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provider naming should follow Dart naming conventions. Variable names should use lowerCamelCase, not UpperCamelCase. This should be gitHubContributorServiceProvider instead of GitHubContributorServiceProvider.
Copilot
AI
Dec 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent provider usage. Line 54 uses githubContributionRepositoryProvider which is the repository provider, but the test should be reading the service provider. This appears to be testing the repository directly instead of the service layer.
| final service = container.read(githubContributionRepositoryProvider); | |
| final service = container.read(GitHubContributorServiceProvider); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| // Mocks generated by Mockito 5.4.6 from annotations | ||
| // in dotto/test/feature/github_contributors/github_contributor_service_test.dart. | ||
| // Do not manually edit this file. | ||
|
|
||
| // ignore_for_file: no_leading_underscores_for_library_prefixes | ||
| import 'dart:async' as _i3; | ||
|
|
||
| import 'package:dotto/domain/github_profile.dart' as _i4; | ||
| import 'package:dotto/repository/github_contoributor_repository.dart' as _i2; | ||
| import 'package:mockito/mockito.dart' as _i1; | ||
|
|
||
| // ignore_for_file: type=lint | ||
| // ignore_for_file: avoid_redundant_argument_values | ||
| // ignore_for_file: avoid_setters_without_getters | ||
| // ignore_for_file: comment_references | ||
| // ignore_for_file: deprecated_member_use | ||
| // ignore_for_file: deprecated_member_use_from_same_package | ||
| // ignore_for_file: implementation_imports | ||
| // ignore_for_file: invalid_use_of_visible_for_testing_member | ||
| // ignore_for_file: must_be_immutable | ||
| // ignore_for_file: prefer_const_constructors | ||
| // ignore_for_file: unnecessary_parenthesis | ||
| // ignore_for_file: camel_case_types | ||
| // ignore_for_file: subtype_of_sealed_class | ||
| // ignore_for_file: invalid_use_of_internal_member | ||
|
|
||
| /// A class which mocks [GitHubContributorRepository]. | ||
| /// | ||
| /// See the documentation for Mockito's code generation for more information. | ||
| class MockGitHubContributorRepository extends _i1.Mock | ||
| implements _i2.GitHubContributorRepository { | ||
| MockGitHubContributorRepository() { | ||
| _i1.throwOnMissingStub(this); | ||
| } | ||
|
|
||
| @override | ||
| _i3.Future<List<_i4.GitHubProfile>> getContributors() => | ||
| (super.noSuchMethod( | ||
| Invocation.method(#getContributors, []), | ||
| returnValue: _i3.Future<List<_i4.GitHubProfile>>.value( | ||
| <_i4.GitHubProfile>[], | ||
| ), | ||
| ) | ||
| as _i3.Future<List<_i4.GitHubProfile>>); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The import path contains a typo: "contoributor" should be "contributor".