mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-27 17:29:50 +08:00
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:
@ -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}';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
90
lib/setup/autoconfigure_complete.dart
Normal file
90
lib/setup/autoconfigure_complete.dart
Normal 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);
|
||||
}
|
||||
}
|
228
lib/setup/repo_selector.dart
Normal file
228
lib/setup/repo_selector.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user