Files
GitJournal/lib/setup/repo_selector.dart
Vishesh Handa 721153308e RepoSelector: Show the datetime in relative terms
Unless it is more than 30 days
2020-09-03 21:50:23 +02:00

340 lines
9.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:function_types/function_types.dart';
import 'package:intl/intl.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:gitjournal/analytics.dart';
import 'package:gitjournal/apis/githost_factory.dart';
import 'package:gitjournal/error_reporting.dart';
import 'package:gitjournal/setup/button.dart';
import 'package:gitjournal/setup/error.dart';
import 'package:gitjournal/setup/loading.dart';
import 'package:gitjournal/utils/logger.dart';
//import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class GitHostSetupRepoSelector extends StatefulWidget {
final GitHost gitHost;
final Func1<GitHostRepo, void> onDone;
GitHostSetupRepoSelector({
@required this.gitHost,
@required this.onDone,
});
@override
GitHostSetupRepoSelectorState createState() {
return GitHostSetupRepoSelectorState();
}
}
class GitHostSetupRepoSelectorState extends State<GitHostSetupRepoSelector> {
String errorMessage = "";
List<GitHostRepo> repos = [];
var fetchedRepos = false;
GitHostRepo selectedRepo;
var _textController = TextEditingController();
bool createRepo = false;
@override
void initState() {
super.initState();
_textController.addListener(() {
setState(() {
selectedRepo = null;
createRepo = false;
});
});
_initStateAysnc();
}
void _initStateAysnc() async {
Log.d("Starting RepoSelector");
try {
var allRepos = await widget.gitHost.listRepos();
allRepos.sort((GitHostRepo a, GitHostRepo b) {
if (a.updatedAt != null && b.updatedAt != null) {
return a.updatedAt.compareTo(b.updatedAt);
}
if (a.updatedAt == null && b.updatedAt == null) {
return a.fullName.compareTo(b.fullName);
}
if (a.updatedAt == null) {
return 1;
}
return -1;
});
if (!mounted) return;
setState(() {
repos = allRepos.reversed.toList();
fetchedRepos = true;
});
var repo = repos.firstWhere(
(r) => r.fullName.endsWith('/journal'),
orElse: () => null,
);
if (repo != null) {
setState(() {
selectedRepo = repo;
});
} else {
setState(() {
_textController.text = "journal";
createRepo = true;
});
}
} on Exception catch (e, stacktrace) {
_handleGitHostException(e, stacktrace);
return;
}
}
void _handleGitHostException(Exception e, StackTrace stacktrace) {
Log.d("GitHostSetupAutoConfigure: " + e.toString());
setState(() {
errorMessage = e.toString();
logEvent(Event.GitHostSetupError, parameters: {
'errorMessage': errorMessage,
});
logException(e, stacktrace);
});
}
@override
Widget build(BuildContext context) {
if (errorMessage != null && errorMessage.isNotEmpty) {
return GitHostSetupErrorPage(errorMessage);
}
if (!fetchedRepos) {
return GitHostSetupLoadingPage("Loading");
}
var q = _textController.text.toLowerCase();
var filteredRepos = repos.where((r) {
var repoName = r.fullName.split('/').last;
return repoName.toLowerCase().contains(q);
});
var repoBuilder = ListView(
children: <Widget>[
if (_textController.text.isNotEmpty) _buildCreateRepoTile(),
for (var repo in filteredRepos)
_RepoTile(
repo: repo,
onTap: () {
setState(() {
selectedRepo = repo;
createRepo = false;
});
},
selected: repo == selectedRepo,
),
],
padding: const EdgeInsets.all(0.0),
);
// Add a Filtering bar
// text: Type to search or create
var textField = TextField(
controller: _textController,
maxLines: 1,
decoration: InputDecoration(
hintText: tr('setup.repoSelector.hint'),
border: const OutlineInputBorder(),
suffixIcon: IconButton(
onPressed: () => _textController.clear(),
icon: const Icon(Icons.clear),
),
),
);
bool canContinue = selectedRepo != null || createRepo;
var columns = Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
tr('setup.repoSelector.title'),
style: Theme.of(context).textTheme.headline6,
),
const SizedBox(height: 16.0),
textField,
const SizedBox(height: 8.0),
Expanded(child: repoBuilder),
const SizedBox(height: 8.0),
Opacity(
opacity: canContinue ? 1.0 : 0.0,
child: GitHostSetupButton(
text: tr('setup.repoSelector.next'),
onPressed: () async {
if (selectedRepo != null) {
widget.onDone(selectedRepo);
return;
}
try {
var repoName = _textController.text.trim();
var repo = await widget.gitHost.createRepo(repoName);
widget.onDone(repo);
return;
} catch (e, stacktrace) {
_handleGitHostException(e, stacktrace);
}
},
),
),
const SizedBox(height: 32.0),
],
);
return SafeArea(child: Center(child: columns));
}
Widget _buildCreateRepoTile() {
var repoName = _textController.text.trim();
return ListTile(
leading: const Icon(Icons.add),
title: Text(tr('setup.repoSelector.create', args: [repoName])),
contentPadding: const EdgeInsets.all(0.0),
onTap: () {
setState(() {
createRepo = true;
selectedRepo = null;
});
},
selected: createRepo,
);
}
}
class _RepoTile extends StatelessWidget {
final GitHostRepo repo;
final Function onTap;
final bool selected;
_RepoTile({
@required this.repo,
@required this.onTap,
@required this.selected,
});
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var textTheme = theme.textTheme;
/*
var iconsRow = Row(
children: [
if (repo.license != null)
_IconText(repo.license, FontAwesomeIcons.balanceScale),
if (repo.license != null) const SizedBox(width: 8.0),
_IconText(repo.forks.toString(), FontAwesomeIcons.codeBranch),
const SizedBox(width: 8.0),
_IconText(repo.stars.toString(), FontAwesomeIcons.star),
],
crossAxisAlignment: CrossAxisAlignment.center,
);
return Card(
margin: const EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0),
elevation: 0.0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(
repo.fullName,
style:
textTheme.headline6.copyWith(color: theme.primaryColorDark),
),
if (repo.description != null) const SizedBox(height: 8.0),
if (repo.description != null) Text(repo.description),
const SizedBox(height: 16.0),
iconsRow,
],
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
),
),
); */
return ListTile(
title: Text(repo.fullName),
trailing: _SmartDateTime(repo.updatedAt, textTheme.caption),
selected: selected,
contentPadding: const EdgeInsets.all(0.0),
onTap: onTap,
);
}
}
/*
class _IconText extends StatelessWidget {
final String text;
final IconData iconData;
_IconText(this.text, this.iconData);
@override
Widget build(BuildContext context) {
var iconTheme = Theme.of(context).iconTheme;
var iconColor = iconTheme.color.withAlpha(150);
var textTheme = Theme.of(context).textTheme;
return Row(
children: [
FaIcon(iconData, size: 16, color: iconColor),
const SizedBox(width: 4.0),
Text(text, style: textTheme.caption),
],
textBaseline: TextBaseline.alphabetic,
crossAxisAlignment: CrossAxisAlignment.baseline,
mainAxisSize: MainAxisSize.min,
);
}
}
*/
class _SmartDateTime extends StatelessWidget {
final DateTime dt;
final TextStyle style;
_SmartDateTime(this.dt, this.style);
static final _dateFormat = DateFormat('d MMM yyyy');
static final _dateFormatWithoutYear = DateFormat('d MMM');
static final thirtyDaysAgo =
DateTime.now().subtract(const Duration(days: 30));
@override
Widget build(BuildContext context) {
if (dt == null) {
return Container();
}
String text;
if (dt.isAfter(thirtyDaysAgo)) {
Locale locale = Localizations.localeOf(context);
text = timeago.format(dt, locale: locale.languageCode);
} else if (dt.year == DateTime.now().year) {
text = _dateFormatWithoutYear.format(dt);
} else {
text = _dateFormat.format(dt);
}
return Text(text, style: style);
}
}