Skip to content

Commit

Permalink
Merge pull request #43 from LeoAndo/release-v1.0.1
Browse files Browse the repository at this point in the history
Release v1.0.1
  • Loading branch information
LeoAndo authored Feb 16, 2023
2 parents 7fc7d42 + ee2a999 commit 24674c5
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 82 deletions.
61 changes: 51 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,57 @@
# flutter_github_search
# 概要

A new Flutter project.
Flutterで作るGithubリポジトリ検索アプリになります。<br>
<strong>本プロジェクトは android / ios / macosのみ動作サポートしています。</strong><br>

## Getting Started
# アーキテクチャ
変更が多い内容ですので、[Wiki](https://github.com/LeoAndo/flutter_github_search/wiki/architecture)にまとめています。<br>

This project is a starting point for a Flutter application.
# 開発環境
- IDE: Visual Studio Code バージョン: 1.75.1 (Universal)

A few resources to get you started if this is your first Flutter project:
# アプリ開発時の準備 (重要)
変更が多い内容ですので、[Wiki](https://github.com/LeoAndo/flutter_github_search/wiki/dev-setup)にまとめています。<br>

- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
# デザイン面:アピールポイント
- Material3 Themeの適用 (カスタマイズ)
- Dark Theme対応

For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
# 機能面:アピールポイント
- Paging対応

# capture: 各プラットフォーム 基本機能の確認

| Android Resizable Emulator API 33 | i phone 14 Pro ios 16.2 | macos |
|:---|:---:|:---:|
|<img src="https://user-images.githubusercontent.com/16476224/218934191-12a8762f-054e-4647-8316-4f4797bcd4bf.gif" width=320 /> |<img src="https://user-images.githubusercontent.com/16476224/218935617-3edd9a0c-fc37-4851-830c-9f78fb8e6df5.gif" width=320 /> | <img src="https://user-images.githubusercontent.com/16476224/218936421-2725e7a5-6b5f-4af0-8254-f29c5de1b434.png" /> |

# capture: 異常系

復旧手段があることの確認<br>
| Android Resizable Emulator API 33 |
|:---|
|<img src="https://user-images.githubusercontent.com/16476224/218940081-918802d6-90bd-498f-b6f8-1d1c40954c4a.gif" width=320 /> |

# capture: Android Material 3の適用チェック

OS12以上でM3のDynamic Colorが適用されていることを確認<br>
[Dynamic Color](https://m3.material.io/styles/color/dynamic-color/user-generated-color) はOS12以上で適用されます。<br>

| Android Resizable Emulator API 33 | Pixel Pro 6 API 26 |
|:---|:---:|
|<img src="https://user-images.githubusercontent.com/16476224/218936947-af4cdcda-8be5-4e30-ba60-e8a20a45ffa7.png" width=320 /> |<img src="https://user-images.githubusercontent.com/16476224/218937498-57f11516-46bc-432d-bfe8-b61fb2bb8263.png" width=320 /> |

# capture: Dark Themeの適用

| Android Resizable Emulator API 33 | i phone 14 Pro ios 16.2 | macos |
|:---|:---:|:---:|
|<img src="https://user-images.githubusercontent.com/16476224/218938289-911a480d-9984-4459-958a-e1166a1d7a46.png" width=320 /> |<img src="https://user-images.githubusercontent.com/16476224/218938512-b07600ca-0022-4c54-85a0-10dfa028064c.png" width=320 /> | <img src="https://user-images.githubusercontent.com/16476224/218938858-878e496e-b7fe-46e4-a005-702724804a8c.png" /> |

# 参考にしたリポジトリ

## dartの実装まわり
https://github.com/watanavex/flutter_github_search<br>
## CI/CD周り
https://github.com/yorifuji/flutter_github_actions_template<br>
## 環境(dev/stg/prod)の切り替え
https://zenn.dev/altiveinc/articles/separating-environments-in-flutter<br>
56 changes: 56 additions & 0 deletions lib/ui/component/app_avatar_network_image.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Flutter imports:
import 'package:flutter/material.dart';

// Package imports:
import 'package:logger/logger.dart';

class AppAvatarNetworkImage extends StatefulWidget {
const AppAvatarNetworkImage(
{super.key,
this.height = 200,
this.width = 200,
this.size = 200,
required this.url});
final double height;
final double width;
final double size;
final String url;

@override
State<StatefulWidget> createState() => _AppAvatarNetworkImageState();
}

class _AppAvatarNetworkImageState extends State<AppAvatarNetworkImage> {
bool _showErrorWidget = false;
@override
Widget build(BuildContext context) {
final errorWidget = Icon(
Icons.person,
size: widget.size,
color: Theme.of(context).colorScheme.error,
);
return SizedBox(
height: widget.height,
width: widget.width,
child: CircleAvatar(
backgroundColor: Colors.transparent,
backgroundImage: NetworkImage(widget.url),
onBackgroundImageError: (exception, stackTrace) {
// ArgumentErrorがスローされる. NetworkImageは内部で、httpライブラリを使用している模様.
if (exception is ArgumentError) {
Logger().e('ando exception ${exception.toString()}');
Logger().e('ando stackTrace $stackTrace');
} else {
Logger().e('ando exception $exception, stackTrace $stackTrace');
}
setState(() => _showErrorWidget = true);
},
child: AnimatedOpacity(
opacity: _showErrorWidget ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
child: errorWidget,
),
),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Flutter imports:
import 'package:flutter/material.dart';

// Project imports:
import 'package:flutter_github_search/ui/res/values/strings.dart' as strings;

class AppError extends StatelessWidget {
final String message;
final VoidCallback onReload;
Expand Down Expand Up @@ -35,7 +38,7 @@ class AppError extends StatelessWidget {
ElevatedButton.icon(
onPressed: onReload,
icon: const Icon(Icons.error_outline),
label: const Text('reload'))
label: const Text(strings.reload))
],
),
),
Expand Down
File renamed without changes.
29 changes: 0 additions & 29 deletions lib/ui/components/app_network_image.dart

This file was deleted.

40 changes: 10 additions & 30 deletions lib/ui/detail/detail_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logger/logger.dart';

// Project imports:
import 'package:flutter_github_search/ui/component/app_avatar_network_image.dart';
import '../../domain/exception/api_exceptions.dart';
import '../../domain/model/repository_detail.dart';
import '../../ui/detail/detail_state_notifier.dart';
import '../components/app_error.dart';
import '../components/app_loading.dart';
import '../components/app_network_image.dart';
import '../component/app_error.dart';
import '../component/app_loading.dart';
import '../res/values/strings.dart' as strings;

class DetailScreen extends ConsumerStatefulWidget {
final String ownerName;
Expand Down Expand Up @@ -39,7 +40,7 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Detail Screen'),
title: const Text(strings.titleDetailScreen),
),
body: _buildBody(context, ref),
);
Expand Down Expand Up @@ -80,7 +81,7 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildOwnerImage(repositoryDetail.ownerAvatarUrl),
AppAvatarNetworkImage(url: repositoryDetail.ownerAvatarUrl),
const SizedBox(height: 16),
Text(
repositoryDetail.name,
Expand All @@ -105,43 +106,22 @@ class _DetailScreenState extends ConsumerState<DetailScreen> {
),
const SizedBox(height: 40),
_buildIconWithText(const Icon(Icons.flutter_dash),
'${repositoryDetail.forksCount} forks'),
'${repositoryDetail.forksCount}'.formatForks()),
const SizedBox(height: 16),
_buildIconWithText(const Icon(Icons.star),
'${repositoryDetail.stargazersCount} stars'),
'${repositoryDetail.stargazersCount}'.formatStars()),
const SizedBox(height: 16),
_buildIconWithText(const Icon(Icons.remove_red_eye),
'${repositoryDetail.watchersCount} watchers'),
'${repositoryDetail.watchersCount}'.formatWatchers()),
const SizedBox(height: 16),
_buildIconWithText(const Icon(Icons.task),
'open ${repositoryDetail.openIssuesCount} issues'),
'${repositoryDetail.openIssuesCount}'.formatOpenIssues()),
],
),
),
);
}

Widget _buildOwnerImage(String imageUrl) {
const imageWidthFactor = 0.5;
final size = MediaQuery.of(context).size;
final placeholder = Icon(
Icons.person,
size: size.width * imageWidthFactor,
);
final errorWidget = Icon(
Icons.error_outline,
size: size.width * imageWidthFactor,
);
return FractionallySizedBox(
widthFactor: imageWidthFactor,
child: AppNetworkImage(
imageUrl: imageUrl,
placeholder: placeholder,
errorWidget: errorWidget,
),
);
}

Widget _buildIconWithText(Icon icon, String text) {
return Row(
children: [
Expand Down
5 changes: 4 additions & 1 deletion lib/ui/environment_variable/environment_variable_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

// Project imports:
import '../res/values/strings.dart' as strings;

class EnvironmentVariableScreen extends StatelessWidget {
const EnvironmentVariableScreen({super.key});
@override
Expand All @@ -14,7 +17,7 @@ class EnvironmentVariableScreen extends StatelessWidget {
const githubApiDomain = String.fromEnvironment('githubApiDomain');
return Scaffold(
appBar: AppBar(
title: const Text('Env Variable'),
title: const Text(strings.titleEnvironmentVariableScreen),
),
body: SafeArea(
child: SingleChildScrollView(
Expand Down
8 changes: 4 additions & 4 deletions lib/ui/home/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter_github_search/ui/package_info/package_info_screen.dart';
import 'package:flutter_github_search/ui/search/pagination/search_paging_screen.dart';
import 'package:flutter_github_search/ui/search/search_screen.dart';
import '../environment_variable/environment_variable_screen.dart';
import '../res/values/strings.dart' as strings;

class HomeScreen extends StatelessWidget {
const HomeScreen({super.key, required this.title});
Expand All @@ -17,19 +18,18 @@ class HomeScreen extends StatelessWidget {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: const Center(
child: Text("Home Page."),
child: Text(strings.titleHomeScreen),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
child: Text(
'Paging compatible / non-compatible versions are available.'),
child: Text(strings.homeDrawableHeader),
),
ListTile(
leading: const Icon(Icons.computer),
title: const Text("Environment Variable"),
title: const Text(strings.titleEnvironmentVariableScreen),
onTap: () {
Navigator.push(
context,
Expand Down
18 changes: 18 additions & 0 deletions lib/ui/res/values/strings.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// UIで使うstringリソースをここで定義します。
/// 多言語対応を想定しない前提なので対応する場合は、外部パッケージをご利用ください。
const reload = 'reload';
const titleDetailScreen = 'Detail Screen';
const titleEnvironmentVariableScreen = 'Environment Variable';
const titleSearchScreen = 'Search Screen';
const titleSearchPagingScreen = 'Search Paging Screen';
const titleHomeScreen = 'Home';
const homeDrawableHeader =
'Paging compatible / non-compatible versions are available.';
const listItemIsEmpty = 'result empty..';

extension StringExt on String {
String formatForks() => '$this forks';
String formatStars() => '$this stars';
String formatWatchers() => '$this watchers';
String formatOpenIssues() => 'open $this issues';
}
5 changes: 3 additions & 2 deletions lib/ui/search/pagination/search_paging_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:flutter_github_search/domain/model/repository_summary.dart';
import 'package:flutter_github_search/ui/detail/detail_screen.dart';
import 'package:flutter_github_search/ui/search/pagination/search_paging_notifier.dart';
import 'package:flutter_github_search/ui/search/pagination/ui_state.dart';
import '../../res/values/strings.dart' as strings;

class SearchPagingScreen extends ConsumerStatefulWidget {
const SearchPagingScreen({super.key});
Expand Down Expand Up @@ -40,7 +41,7 @@ class _SearchPagingScreenState extends ConsumerState<SearchPagingScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Search Paging Screen'),
title: const Text(strings.titleSearchPagingScreen),
),
body: _buildBody());
}
Expand Down Expand Up @@ -174,7 +175,7 @@ class _SearchPagingScreenState extends ConsumerState<SearchPagingScreen> {
return uiState.repositories.isEmpty
? (uiState is Data)
? const Center(
child: Text('result empty..'),
child: Text(strings.listItemIsEmpty),
)
: Container()
: NotificationListener<ScrollNotification>(
Expand Down
7 changes: 4 additions & 3 deletions lib/ui/search/search_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import 'package:logger/logger.dart';
import 'package:flutter_github_search/domain/exception/application_exception.dart';
import 'package:flutter_github_search/domain/model/repository_summary.dart';
import 'package:flutter_github_search/ui/detail/detail_screen.dart';
import '../components/app_error.dart';
import '../components/app_loading.dart';
import '../component/app_error.dart';
import '../component/app_loading.dart';
import '../res/values/strings.dart' as strings;
import 'search_state_notifier.dart';

class SearchScreen extends ConsumerStatefulWidget {
Expand Down Expand Up @@ -40,7 +41,7 @@ class _SearchScreenState extends ConsumerState<SearchScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Search Screen'),
title: const Text(strings.titleSearchScreen),
),
body: _buildBody(ref));
}
Expand Down
1 change: 0 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ dependencies:
freezed_annotation: ^2.2.0
json_annotation: ^4.8.0
logger: ^1.1.0
cached_network_image: ^3.2.3
package_info_plus: ^3.0.3

dev_dependencies:
Expand Down
2 changes: 1 addition & 1 deletion test/ui/detail/detail_screen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart';

// Project imports:
import 'package:flutter_github_search/data/repository/github_repo_repository_impl.dart';
import 'package:flutter_github_search/ui/components/app_error.dart';
import 'package:flutter_github_search/ui/component/app_error.dart';
import 'package:flutter_github_search/ui/detail/detail_screen.dart';
import '../../data/repository/fake_github_repo_repository.dart';

Expand Down

0 comments on commit 24674c5

Please sign in to comment.