GitHostSetup: Allow the user to choose between repos

This way they don't need to use a repo called 'journal'.
This commit is contained in:
Vishesh Handa
2020-05-15 15:28:44 +02:00
parent 2cc1707ef7
commit 18db4899b2
7 changed files with 381 additions and 43 deletions

View File

@ -12,7 +12,7 @@ abstract class GitHost {
Future<List<GitHostRepo>> listRepos();
Future<GitHostRepo> createRepo(String name);
Future<GitHostRepo> getRepo(String name);
Future addDeployKey(String sshPublicKey, String repo);
Future addDeployKey(String sshPublicKey, String repoFullName);
}
class UserInfo {
@ -28,14 +28,19 @@ class UserInfo {
}
class GitHostRepo {
String fullName;
String cloneUrl;
final String fullName;
final String cloneUrl;
final DateTime updatedAt;
GitHostRepo({this.fullName, this.cloneUrl});
GitHostRepo({
@required this.fullName,
@required this.cloneUrl,
@required this.updatedAt,
});
@override
String toString() {
return 'GitRepo{fulleName: $fullName, cloneUrl: $cloneUrl}';
return 'GitRepo{fulleName: $fullName, cloneUrl: $cloneUrl, updatedAt: $updatedAt}';
}
}

View File

@ -210,9 +210,16 @@ class GitHub implements GitHost {
}
GitHostRepo _repoFromJson(Map<String, dynamic> parsedJson) {
DateTime updatedAt;
try {
updatedAt = DateTime.parse(parsedJson['updated_at'].toString());
} catch (e) {
Log.e(e);
}
return GitHostRepo(
fullName: parsedJson['full_name'],
cloneUrl: parsedJson['ssh_url'],
updatedAt: updatedAt,
);
}

View File

@ -194,9 +194,17 @@ class GitLab implements GitHost {
}
GitHostRepo _repoFromJson(Map<String, dynamic> parsedJson) {
DateTime updatedAt;
try {
updatedAt = DateTime.parse(parsedJson['last_activity_at'].toString());
} catch (e) {
Log.e(e);
}
return GitHostRepo(
fullName: parsedJson['path_with_namespace'],
cloneUrl: parsedJson['ssh_url_to_repo'],
updatedAt: updatedAt,
);
}

View File

@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:function_types/function_types.dart';
import 'package:git_bindings/git_bindings.dart';
import 'package:gitjournal/analytics.dart';
import 'package:gitjournal/apis/githost_factory.dart';
import 'package:gitjournal/error_reporting.dart';
@ -16,7 +14,7 @@ import 'loading.dart';
class GitHostSetupAutoConfigure extends StatefulWidget {
final GitHostType gitHostType;
final Func1<String, void> onDone;
final Func1<GitHost, void> onDone;
GitHostSetupAutoConfigure({
@required this.gitHostType,
@ -50,35 +48,11 @@ class GitHostSetupAutoConfigureState extends State<GitHostSetupAutoConfigure> {
}
Log.d("GitHost Initalized: " + widget.gitHostType.toString());
GitHostRepo repo;
try {
setState(() {
_message = "Creating private repo";
_message = "Reading User Info";
});
try {
repo = await gitHost.createRepo("journal");
} on GitHostException catch (e) {
if (e.cause != GitHostException.RepoExists.cause) {
rethrow;
}
setState(() {
_message = "Using existing repo";
});
repo = await gitHost.getRepo("journal");
}
setState(() {
_message = "Generating SSH Key";
});
var publicKey = await generateSSHKeys(comment: "GitJournal");
setState(() {
_message = "Adding as a Deploy Key";
});
await gitHost.addDeployKey(publicKey, repo.fullName);
var userInfo = await gitHost.getUserInfo();
if (userInfo.name != null && userInfo.name.isNotEmpty) {
Settings.instance.gitAuthor = userInfo.name;
@ -91,7 +65,7 @@ class GitHostSetupAutoConfigureState extends State<GitHostSetupAutoConfigure> {
_handleGitHostException(e, stacktrace);
return;
}
widget.onDone(repo.cloneUrl);
widget.onDone(gitHost);
});
try {
@ -143,7 +117,7 @@ class GitHostSetupAutoConfigureState extends State<GitHostSetupAutoConfigure> {
// Step 1
Text(
"1. Create a new private repo called 'journal' or use the existing one",
"1. List your existing repos or create a new repo",
style: Theme.of(context).textTheme.bodyText1,
),
const SizedBox(height: 8.0),

View File

@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:function_types/function_types.dart';
import 'package:git_bindings/git_bindings.dart';
import 'package:gitjournal/analytics.dart';
import 'package:gitjournal/apis/githost_factory.dart';
import 'package:gitjournal/error_reporting.dart';
import 'package:gitjournal/utils/logger.dart';
import 'error.dart';
import 'loading.dart';
class GitHostSetupAutoConfigureComplete extends StatefulWidget {
final GitHost gitHost;
final GitHostRepo repo;
final Func1<String, void> onDone;
GitHostSetupAutoConfigureComplete({
@required this.gitHost,
@required this.repo,
@required this.onDone,
});
@override
GitHostSetupAutoConfigureCompleteState createState() {
return GitHostSetupAutoConfigureCompleteState();
}
}
class GitHostSetupAutoConfigureCompleteState
extends State<GitHostSetupAutoConfigureComplete> {
String errorMessage = "";
String _message = "...";
@override
void initState() {
super.initState();
_initAsync();
}
void _initAsync() async {
Log.d("Starting autoconfigure copletion");
try {
Log.i("Generating SSH Key");
setState(() {
_message = "Generating SSH Key";
});
var publicKey = await generateSSHKeys(comment: "GitJournal");
Log.i("Adding as a deploy key");
if (mounted) {
setState(() {
_message = "Adding as a Deploy Key";
});
}
await widget.gitHost.addDeployKey(publicKey, widget.repo.fullName);
} on Exception catch (e, stacktrace) {
_handleGitHostException(e, stacktrace);
return;
}
widget.onDone(widget.repo.cloneUrl);
}
void _handleGitHostException(Exception e, StackTrace stacktrace) {
Log.d("GitHostSetupAutoConfigureComplete: " + e.toString());
setState(() {
errorMessage = e.toString();
getAnalytics().logEvent(
name: "githostsetup_error",
parameters: <String, String>{
'errorMessage': errorMessage,
},
);
logException(e, stacktrace);
});
}
@override
Widget build(BuildContext context) {
if (errorMessage == null || errorMessage.isEmpty) {
return GitHostSetupLoadingPage(_message);
}
return GitHostSetupErrorPage(errorMessage);
}
}

View File

@ -0,0 +1,228 @@
import 'package:flutter/material.dart';
import 'package:function_types/function_types.dart';
import 'package:gitjournal/analytics.dart';
import 'package:gitjournal/apis/githost_factory.dart';
import 'package:gitjournal/error_reporting.dart';
import 'package:gitjournal/utils/logger.dart';
import 'package:intl/intl.dart';
import 'button.dart';
import 'error.dart';
import 'loading.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();
getAnalytics().logEvent(
name: "githostsetup_error",
parameters: <String, String>{
'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) _buildRepoTile(repo),
],
padding: const EdgeInsets.all(0.0),
);
// Add a Filtering bar
// text: Type to search or create
var textField = TextField(
controller: _textController,
maxLines: 1,
decoration: const InputDecoration(
hintText: 'Type to Search or Create a Repo',
border: OutlineInputBorder(),
),
);
bool canContinue = selectedRepo != null || createRepo;
var columns = Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Choose or create a repository -',
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: "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 _buildRepoTile(GitHostRepo repo) {
final _dateFormat = DateFormat('dd MMM, yyyy');
Widget trailing = Container();
if (repo.updatedAt != null) {
var dateStr = _dateFormat.format(repo.updatedAt);
var textTheme = Theme.of(context).textTheme;
trailing = Text(dateStr, style: textTheme.caption);
}
return ListTile(
title: Text(repo.fullName),
trailing: trailing,
selected: repo == selectedRepo,
onTap: () {
setState(() {
selectedRepo = repo;
createRepo = false;
});
},
contentPadding: const EdgeInsets.all(0.0),
);
}
Widget _buildCreateRepoTile() {
var repoName = _textController.text.trim();
return ListTile(
leading: const Icon(Icons.add),
title: Text('Create repo "$repoName"'),
contentPadding: const EdgeInsets.all(0.0),
onTap: () {
setState(() {
createRepo = true;
selectedRepo = null;
});
},
selected: createRepo,
);
}
}

View File

@ -9,6 +9,8 @@ import 'package:git_bindings/git_bindings.dart';
import 'package:gitjournal/analytics.dart';
import 'package:gitjournal/apis/githost_factory.dart';
import 'package:gitjournal/setup/autoconfigure_complete.dart';
import 'package:gitjournal/setup/repo_selector.dart';
import 'package:gitjournal/state_container.dart';
import 'package:gitjournal/utils.dart';
import 'package:gitjournal/settings.dart';
@ -49,6 +51,9 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
var _keyGenerationChoice = KeyGenerationChoice.Unknown;
var _gitHostType = GitHostType.Unknown;
GitHost _gitHost;
GitHostRepo _gitHostRepo;
var _gitCloneUrl = "";
var gitCloneErrorMessage = "";
var publicKey = "";
@ -156,13 +161,12 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
} else if (_pageChoice[1] == PageChoice1.Auto) {
return GitHostSetupAutoConfigure(
gitHostType: _gitHostType,
onDone: (String gitCloneUrl) {
onDone: (GitHost gitHost) {
setState(() {
_gitCloneUrl = gitCloneUrl;
_gitHost = gitHost;
_pageCount = pos + 2;
_nextPage();
_startGitClone(context);
});
},
);
@ -225,7 +229,16 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
},
);
} else if (_pageChoice[1] == PageChoice1.Auto) {
return GitHostSetupGitClone(errorMessage: gitCloneErrorMessage);
return GitHostSetupRepoSelector(
gitHost: _gitHost,
onDone: (GitHostRepo repo) {
setState(() {
_gitHostRepo = repo;
_pageCount = pos + 2;
_nextPage();
});
},
);
}
assert(false);
@ -271,18 +284,30 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
},
);
}
} else if (_pageChoice[1] == PageChoice1.Auto) {
return GitHostSetupAutoConfigureComplete(
gitHost: _gitHost,
repo: _gitHostRepo,
onDone: (String gitCloneUrl) {
setState(() {
_gitCloneUrl = gitCloneUrl;
_pageCount = pos + 2;
_nextPage();
_startGitClone(context);
});
},
);
}
}
if (pos == 5) {
if (_pageChoice[1] == PageChoice1.Manual) {
return GitHostSetupGitClone(errorMessage: gitCloneErrorMessage);
}
return GitHostSetupGitClone(errorMessage: gitCloneErrorMessage);
}
assert(_pageChoice[0] != PageChoice0.CustomProvider);
assert(false);
assert(false, "Pos is $pos");
return null;
}
@ -450,6 +475,7 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
Log.d("Cloning " + _gitCloneUrl);
await GitRepo.clone(repoPath, _gitCloneUrl);
} on GitException catch (e) {
Log.e(e.toString());
error = e.cause;
}