Skip to content
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
52 changes: 52 additions & 0 deletions lib/cubits/github/github_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'package:dio/dio.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hacki/cubits/github/github_states.dart';
import 'package:hacki/models/github/github_repository.dart';

class GithubCubit extends Cubit<GithubState> {
GithubCubit({Dio? dio})
: _dio = dio ?? Dio(),
super(const GithubState());

final Dio _dio;
static const String _githubApiUrl = 'https://api.github.com/repos/';
static const String _token = 'YOUR_GITHUB_PAT_ADMIN';

Future<void> fetchRepository(String url) async {
if (state.repositories.containsKey(url)) return;
if (!_isValidGithubUrl(url)) return;

emit(state.copyWith(status: GithubStatus.loading));

try {
final String apiUrl = url.replaceFirst(
'https://github.com/',
_githubApiUrl,
);

final response = await _dio.get(
apiUrl,
options: Options(
headers: {'Authorization': 'Bearer $_token'},
),
);

final GithubRepository repository =
GithubRepository.fromJson(response.data as Map<String, dynamic>);

emit(state.copyWith(
repositories: {...state.repositories, url: repository},
status: GithubStatus.success,
));
} catch (e) {
emit(state.copyWith(
status: GithubStatus.failure,
error: e.toString(),
));
}
}

bool _isValidGithubUrl(String url) {
return RegExp(r'^https://github\.com/[\w\-]+/[\w\-]+$').hasMatch(url);
}
}
31 changes: 31 additions & 0 deletions lib/cubits/github/github_states.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:equatable/equatable.dart';
import 'package:hacki/models/github/github_repository.dart';

enum GithubStatus { initial, loading, success, failure }

class GithubState extends Equatable {
const GithubState({
this.repositories = const <String, GithubRepository>{},
this.status = GithubStatus.initial,
this.error,
});

final Map<String, GithubRepository> repositories;
final GithubStatus status;
final String? error;

GithubState copyWith({
Map<String, GithubRepository>? repositories,
GithubStatus? status,
String? error,
}) {
return GithubState(
repositories: repositories ?? this.repositories,
status: status ?? this.status,
error: error ?? this.error,
);
}

@override
List<Object?> get props => [repositories, status, error];
}
46 changes: 46 additions & 0 deletions lib/models/github/github_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:equatable/equatable.dart';

class GithubRepository extends Equatable {
const GithubRepository({
required this.fullName,
required this.description,
required this.stars,
required this.language,
required this.license,
required this.watching,
required this.forks,
});

final String fullName;
final String description;
final int stars;
final String language;
final String license;
final int watching;
final int forks;

@override
List<Object?> get props => <Object?>[
fullName,
description,
stars,
language,
license,
watching,
forks,
];

factory GithubRepository.fromJson(Map<String, dynamic> json) {
return GithubRepository(
fullName: json['full_name'] as String? ?? '',
description: json['description']?.toString() ?? '',
stars: json['stargazers_count'] as int? ?? 0,
language: json['language']?.toString() ?? '',
license:
(json['license'] as Map<String, dynamic>?)?['name']?.toString() ??
'No License',
watching: json['subscribers_count'] as int? ?? 0,
forks: json['forks_count'] as int? ?? 0,
);
}
}
87 changes: 87 additions & 0 deletions lib/screens/item/widgets/github_repository_card.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hacki/cubits/github/github_cubit.dart';
import 'package:hacki/cubits/github/github_states.dart';

class GithubRepositoryCard extends StatelessWidget {
const GithubRepositoryCard({
required this.url,
super.key,
});

final String url;

@override
Widget build(BuildContext context) {
return BlocBuilder<GithubCubit, GithubState>(
builder: (context, state) {
final repository = state.repositories[url];

if (repository == null) {
if (state.status == GithubStatus.loading) {
return const Center(child: CircularProgressIndicator());
}
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.all(10),
child: Container(
height: 210,
decoration: BoxDecoration(
border: Border.all(
width: 2,
),
borderRadius: const BorderRadius.all(Radius.circular(25)),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
repository.fullName,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(repository.description),
Row(
children: <Widget>[
const Icon(Icons.star_border_sharp),
const SizedBox(width: 5),
Text('${repository.stars} Stars'),
const Spacer(),
const Icon(Icons.file_copy_sharp),
const SizedBox(width: 5),
Text(repository.license),
],
),
Row(
children: <Widget>[
const Icon(Icons.remove_red_eye_sharp),
const SizedBox(width: 5),
Text('${repository.watching} watching'),
const Spacer(),
const Icon(Icons.code),
const SizedBox(width: 5),
Text(repository.language),
],
),
Row(
children: <Widget>[
const Icon(Icons.fork_right_sharp),
const SizedBox(width: 5),
Text('${repository.forks} forks'),
],
),
],
),
),
),
);
},
);
}
}
Loading