Skip to content

Commit

Permalink
feat(readme): Add readme loading support
Browse files Browse the repository at this point in the history
  • Loading branch information
Grohden committed Aug 2, 2020
1 parent bb4ab10 commit c6b67e6
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 38 deletions.
13 changes: 11 additions & 2 deletions backend/src/com/grohden/repotagger/api/Repository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.grohden.repotagger.dao.tables.User
import com.grohden.repotagger.dao.tables.UserTagDTO
import com.grohden.repotagger.dao.tables.toDTOList
import com.grohden.repotagger.github.api.GithubClient
import com.grohden.repotagger.github.api.RepositoryOwner
import io.ktor.application.call
import io.ktor.auth.authenticate
import io.ktor.auth.authentication
Expand Down Expand Up @@ -35,8 +36,14 @@ data class DetailedRepository(
@SerializedName("stargazers_count")
val stargazersCount: Int,

@SerializedName("forks_count")
val forksCount: Int,

@SerializedName("user_tags")
val userTags: List<UserTagDTO>
val userTags: List<UserTagDTO>,

@SerializedName("readme_url")
val readmeUrl: String
)

fun Route.repository(dao: DAOFacade, github: GithubClient) {
Expand Down Expand Up @@ -88,7 +95,9 @@ fun Route.repository(dao: DAOFacade, github: GithubClient) {
url = githubRepo.url,
language = githubRepo.language,
stargazersCount = githubRepo.stargazersCount,
userTags = tags
userTags = tags,
readmeUrl = githubRepo.readmeUrl,
forksCount = githubRepo.forksCount
)
)
}
Expand Down
26 changes: 23 additions & 3 deletions backend/src/com/grohden/repotagger/github/api/GithubClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,42 @@ import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.get
import io.ktor.client.request.header


private const val API_BASE = "https://api.github.com"
private const val RAW_API_BASE = "https://raw.githubusercontent.com"

data class RepositoryOwner(
val login: String
)

data class GithubRepository(
val id: Int,
val name: String,
val description: String,
val url: String,
val language: String?,
val topics: List<String>,
val owner: RepositoryOwner,

@SerializedName("default_branch")
val defaultBranch: String,

@SerializedName("contents_url")
val contentsUrl: String,

@SerializedName("stargazers_count")
val stargazersCount: Int,

@SerializedName("forks_count")
val forksCount: Int
)
) {

// May not be really available (no readme)
// FIXME: this could be improved and built on
// on the facade layer (or service when available)
val readmeUrl: String
get() = "$RAW_API_BASE/${owner.login}/${name}/${defaultBranch}/README.md"
}

typealias GithubRepositories = List<GithubRepository>

Expand All @@ -28,8 +50,6 @@ data class UserData(
val login: String
)

private const val API_BASE = "https://api.github.com"

/**
* Github api client
*
Expand Down
4 changes: 4 additions & 0 deletions frontend/lib/api/tagger/repository_tagger_models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class DetailedSourceRepository {
@required this.stargazersCount,
@required this.userTags,
@required this.forksCount,
@required this.readmeUrl,
});

final int id;
Expand All @@ -105,6 +106,9 @@ class DetailedSourceRepository {
@JsonKey(name: 'forks_count')
final int forksCount;

@JsonKey(name: 'readme_url')
final String readmeUrl;

factory DetailedSourceRepository.fromJson(Map<String, dynamic> json) =>
_$DetailedSourceRepositoryFromJson(json);

Expand Down
1 change: 1 addition & 0 deletions frontend/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class TaggerApp extends StatelessWidget {
accentColor: Colors.lightBlueAccent,
),
initialRoute: Routes.splash,
navigatorKey: Get.key,
getPages: [
GetPage(
name: Routes.splash,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ class RepositoryDetailsController extends GetxController {

final showLoading = false.obs;
final hasLoadError = false.obs;
final repo = Rx<DetailedSourceRepository>(null);
final repository = Rx<DetailedSourceRepository>(null);

final showReadmeLoading = false.obs;
final readmeLoadError = false.obs;
final readmeContents = ''.obs;

List<UserTag> _allUserTags;

Expand All @@ -24,7 +28,12 @@ class RepositoryDetailsController extends GetxController {
hasLoadError.value = false;

try {
repo.value = await tagger.detailedRepo(repoId);
repository.value = await tagger.detailedRepo(repoId);

// Theoretically we should'nt need to await readme load
// but this **provably** causes a race condition
// on get set state fn
await loadReadme();
} on Exception catch (error) {
print(error);
hasLoadError.value = true;
Expand All @@ -33,10 +42,35 @@ class RepositoryDetailsController extends GetxController {
}
}

/// Loads the readme contents from the repository,
/// may fail for unknown reasons, and in that case
/// it flags [readmeLoadError] as true
Future loadReadme() async {
try {
showReadmeLoading.value = true;
readmeLoadError.value = false;
print(repository.value.readmeUrl);

// We don't want to use the app dio instance
// because it's meant to be used with repository tagger
// and adds some unnecessary interceptors
final response = await Dio().get<String>(repository.value.readmeUrl);

readmeContents.value = response.data;
} on DioError catch (error) {
readmeLoadError.value = true;
print(error);
} finally {
showReadmeLoading.value = false;
}
}

/// Adds new tag into the current repository
/// in case the flag name is already present
/// at [repository] userTags this call is a noop
Future<UserTag> addTag(UserTag tag) async {
final foundTag = repo.value.userTags.firstWhere(
(current) => current.name.toLowerCase() == tag.name.toLowerCase(),
final foundTag = repository.value.userTags.firstWhere(
(current) => current.name.toLowerCase() == tag.name.toLowerCase(),
orElse: () => null,
);

Expand All @@ -59,6 +93,8 @@ class RepositoryDetailsController extends GetxController {
return null;
}

/// Removes a tag from a repository
/// server errors are ignored
void removeTag(UserTag tag) async {
try {
tagger.removeTag(
Expand All @@ -70,8 +106,13 @@ class RepositoryDetailsController extends GetxController {
}
}

/// Finds a tags suggestion list
///
/// It uses the user tags endpoint and caches it, tags
/// are then matched with [query] and removed if
/// they're found on [currentTags]
Future<List<UserTag>> findSuggestions(String query) async {
final currentTags = repo.value.userTags;
final currentTags = repository.value.userTags;

if (_allUserTags == null) {
_allUserTags = await tagger.userTags();
Expand Down
106 changes: 81 additions & 25 deletions frontend/lib/ui/pages/repository_details/repository_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import 'package:get/get.dart';
import '../../../api/tagger/repository_tagger_client.dart';
import '../../../services/session_service.dart';
import '../../molecules/load_page_error.dart';
import '../../templates/page_body.dart';
import '../../templates/two_slot_container.dart';
import 'widets/readme_container.dart';
import 'widets/tags_container.dart';

part 'repository_details_bindings.dart';
Expand Down Expand Up @@ -36,36 +37,91 @@ class RepositoryDetailsPage extends GetView<RepositoryDetailsController> {
return const CircularProgressIndicator();
}

const padding = EdgeInsets.all(16.0);
return SafeArea(
child: TwoSlotContainer(
leftWidth: 425,
leftSlot: Padding(
padding: padding,
child: _buildLeft(context),
),
rightSlot: Scrollbar(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: padding,
child: _buildRight(context),
),
),
),
);
}

Widget _buildRight(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final repository = controller.repo.value;
final repository = controller.repository.value;

return SafeArea(
child: PageBody(
child: SizedBox.expand(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
return SliverFillRemaining(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildTitle(context),
const SizedBox(height: 16),
Text(
repository.description,
style: textTheme.bodyText1,
),
const SizedBox(height: 32),
ReadmeContainer(),
],
),
);
}

Widget _buildLeft(BuildContext context) {
final textTheme = Theme.of(context).textTheme;

return Column(
children: [
Text(
'Personal tags',
style: textTheme.headline6,
),
const SizedBox(height: 16),
TagsContainer(),
],
);
}

Widget _buildTitle(BuildContext context) {
final repository = controller.repository.value;
final textTheme = Theme.of(context).textTheme;

return Row(
children: [
Expanded(
child: Text(
repository.name,
style: textTheme.headline3,
),
),
if (!repository.language.isNullOrBlank)
Chip(
visualDensity: VisualDensity.compact,
label: Text(repository.language),
),
const SizedBox(width: 8),
Chip(
visualDensity: VisualDensity.compact,
label: Row(
children: [
Text(
repository.name,
style: textTheme.headline3,
),
const SizedBox(height: 16),
Text(
repository.description,
style: textTheme.bodyText1,
),
const SizedBox(height: 32),
Text(
'User tags',
style: textTheme.headline6,
),
const SizedBox(height: 16),
TagsContainer()
const Icon(Icons.star_border, size: 14),
const SizedBox(width: 4),
Text(repository.stargazersCount.toString()),
],
),
),
),
],
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:get/get.dart';
import 'package:markdown/markdown.dart' as md;
import '../../../molecules/load_page_error.dart';

import '../repository_details_page.dart';

/// Controls exhibition of the repository readme
/// [RepositoryDetailsController] is required to be injected.
class ReadmeContainer extends GetView<RepositoryDetailsController> {
@override
Widget build(BuildContext context) {
if (controller.showReadmeLoading.value || true) {
return const Center(child: CircularProgressIndicator());
}

if (controller.readmeLoadError.value) {
return const LoadPageError();
}

final readmeContents = controller.readmeContents.value;
final theme = Theme.of(context);

return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: theme.dividerColor,
width: theme.dividerTheme.thickness ?? 1,
)),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 32.0,
),
child: MarkdownBody(
data: readmeContents,
extensionSet: md.ExtensionSet.gitHubWeb,
),
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class TagsContainer extends GetView<RepositoryDetailsController> {
// FIXME: underlying third party lib is applying a really
// nice side effect on our list here :DDDD (by removing some items of it)
// for now this is surprisingly.. useful (?)
final tags = controller.repo.value.userTags;
final tags = controller.repository.value.userTags;

return FlutterTagging<UserTag>(
initialItems: tags,
Expand Down
4 changes: 4 additions & 0 deletions frontend/lib/ui/pages/tags/tags_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class TagsController extends GetxController {

void onInit() async {
super.onInit();
load();
}

void load() async {
showLoading.value = true;
hasLoadError.value = false;
try {
Expand Down
Loading

0 comments on commit c6b67e6

Please sign in to comment.