Remove onboarding screen

For now just create a local git repo and commit all the changes over
there, we're going to allow the user to first see the app and use it
however they want, and later connect it to a remote git repo.

This commit breaks the app, as the on-boarding screen is no longer
connected so you cannot push to a remote app.
This commit is contained in:
Vishesh Handa
2019-01-21 13:43:33 +01:00
parent c915e58273
commit 519de8fcff
12 changed files with 239 additions and 76 deletions

View File

@ -0,0 +1,55 @@
package io.gitjournal.gitjournal;
import android.os.AsyncTask;
import android.util.Log;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.SshTransport;
import org.eclipse.jgit.lib.TextProgressMonitor;
import java.io.PrintWriter;
import java.io.File;
import io.flutter.plugin.common.MethodChannel.Result;
public class GitInitTask extends AsyncTask<String, Void, Void> {
private final static String TAG = "GitInit";
private Result result;
public GitInitTask(Result _result) {
result = _result;
}
protected Void doInBackground(String... params) {
String cloneDirPath = params[0];
File cloneDir = new File(cloneDirPath);
Log.d("GitInit Directory", cloneDirPath);
try {
Git.init().setDirectory(cloneDir).call();
} catch (TransportException e) {
Log.d(TAG, e.toString());
result.error("FAILED", e.getMessage(), null);
return null;
} catch (GitAPIException e) {
Log.d(TAG, e.toString());
result.error("FAILED", e.getMessage(), null);
return null;
} catch (Exception e) {
Log.d(TAG, e.toString());
result.error("FAILED", e.getMessage(), null);
return null;
}
result.success(null);
return null;
}
}

View File

@ -146,6 +146,18 @@ public class MainActivity extends FlutterActivity {
new GitCommitTask(result).execute(cloneLocation, authorName, authorEmail, message); new GitCommitTask(result).execute(cloneLocation, authorName, authorEmail, message);
return; return;
} else if (call.method.equals("gitInit")) {
String folderName = call.argument("folderName");
if (folderName == null || folderName.isEmpty()) {
result.error("Invalid Parameters", "folderName Invalid", null);
return;
}
String initLocation = filesDir + "/" + folderName;
new GitInitTask(result).execute(initLocation);
return;
} else if (call.method.equals("generateSSHKeys")) { } else if (call.method.equals("generateSSHKeys")) {
String comment = call.argument("comment"); String comment = call.argument("comment");
if (comment == null || comment.isEmpty()) { if (comment == null || comment.isEmpty()) {

View File

@ -1,7 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:journal/state_container.dart';
import 'package:journal/screens/home_screen.dart'; import 'package:journal/screens/home_screen.dart';
import 'package:journal/screens/onboarding_screens.dart';
import 'package:journal/screens/settings_screen.dart'; import 'package:journal/screens/settings_screen.dart';
import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_analytics/firebase_analytics.dart';
@ -32,14 +30,7 @@ class JournalApp extends StatelessWidget {
navigatorObservers: <NavigatorObserver>[JournalApp.observer], navigatorObservers: <NavigatorObserver>[JournalApp.observer],
initialRoute: '/', initialRoute: '/',
routes: { routes: {
'/': (context) { '/': (context) => HomeScreen(),
final stateContainer = StateContainer.of(context);
var onBoardingDone = stateContainer.appState.onBoardingCompleted;
var home = onBoardingDone
? new HomeScreen()
: new OnBoardingScreen(stateContainer.completeOnBoarding);
return home;
},
'/settings': (context) => SettingsScreen(), '/settings': (context) => SettingsScreen(),
}, },
); );

View File

@ -1,12 +1,21 @@
import 'package:journal/note.dart'; import 'package:journal/note.dart';
class AppState { class AppState {
bool onBoardingCompleted; // FIXME: Make these 2 final
String localGitRepoPath = "";
bool localGitRepoConfigured = false;
// FIXME: Rename from 'path' to folderName
String remoteGitRepoPath = "";
bool remoteGitRepoConfigured = false;
// FIXME: Make final
String gitBaseDirectory = "";
bool isLoadingFromDisk; bool isLoadingFromDisk;
List<Note> notes; List<Note> notes;
AppState({ AppState({
this.onBoardingCompleted = false,
this.isLoadingFromDisk = false, this.isLoadingFromDisk = false,
this.notes = const [], this.notes = const [],
}); });

View File

@ -6,6 +6,7 @@ import 'package:flutter_crashlytics/flutter_crashlytics.dart';
import 'package:journal/app.dart'; import 'package:journal/app.dart';
import 'package:journal/state_container.dart'; import 'package:journal/state_container.dart';
import 'package:journal/storage/git.dart';
void main() async { void main() async {
bool isInDebugMode = true; bool isInDebugMode = true;
@ -30,7 +31,11 @@ void main() async {
Future runJournalApp() async { Future runJournalApp() async {
var pref = await SharedPreferences.getInstance(); var pref = await SharedPreferences.getInstance();
var onBoardingCompleted = pref.getBool("onBoardingCompleted") ?? false; var localGitRepoConfigured = pref.getBool("localGitRepoConfigured") ?? false;
var remoteGitRepoConfigured =
pref.getBool("remoteGitRepoConfigured") ?? false;
var localGitRepoPath = pref.getString("localGitRepoPath") ?? "";
var remoteGitRepoPath = pref.getString("remoteGitRepoPath") ?? "";
if (JournalApp.isInDebugMode) { if (JournalApp.isInDebugMode) {
if (JournalApp.analytics.android != null) { if (JournalApp.analytics.android != null) {
@ -38,8 +43,25 @@ Future runJournalApp() async {
} }
} }
if (localGitRepoConfigured == false) {
// FIXME: What about exceptions!
localGitRepoPath = "journal_local";
await gitInit(localGitRepoPath);
localGitRepoConfigured = true;
await pref.setBool("localGitRepoConfigured", localGitRepoConfigured);
await pref.setString("localGitRepoPath", localGitRepoPath);
}
var dir = await getGitBaseDirectory();
runApp(new StateContainer( runApp(new StateContainer(
onBoardingCompleted: onBoardingCompleted, localGitRepoConfigured: localGitRepoConfigured,
remoteGitRepoConfigured: remoteGitRepoConfigured,
localGitRepoPath: localGitRepoPath,
remoteGitRepoPath: remoteGitRepoPath,
gitBaseDirectory: dir.path,
child: JournalApp(), child: JournalApp(),
)); ));
} }

View File

@ -300,6 +300,8 @@ class OnBoardingScreenState extends State<OnBoardingScreen> {
} }
Future _removeExistingClone() async { Future _removeExistingClone() async {
// FIXME: Implement this
/*
var baseDir = await getNotesDir(); var baseDir = await getNotesDir();
var dotGitDir = new Directory(p.join(baseDir.path, ".git")); var dotGitDir = new Directory(p.join(baseDir.path, ".git"));
bool exists = await dotGitDir.exists(); bool exists = await dotGitDir.exists();
@ -307,6 +309,7 @@ class OnBoardingScreenState extends State<OnBoardingScreen> {
await baseDir.delete(recursive: true); await baseDir.delete(recursive: true);
await baseDir.create(); await baseDir.create();
} }
*/
} }
} }

View File

@ -13,20 +13,20 @@ import 'package:journal/storage/git_storage.dart';
import 'package:journal/storage/git.dart'; import 'package:journal/storage/git.dart';
import 'package:journal/datetime_utils.dart'; import 'package:journal/datetime_utils.dart';
Future<Directory> getNotesDir() async {
var appDir = await getGitBaseDirectory();
var dir = new Directory(p.join(appDir.path, "journal"));
await dir.create();
return dir;
}
class StateContainer extends StatefulWidget { class StateContainer extends StatefulWidget {
final Widget child; final Widget child;
final bool onBoardingCompleted; final bool localGitRepoConfigured;
final bool remoteGitRepoConfigured;
final String localGitRepoPath;
final String remoteGitRepoPath;
final String gitBaseDirectory;
StateContainer({ StateContainer({
@required this.onBoardingCompleted, @required this.localGitRepoConfigured,
@required this.remoteGitRepoConfigured,
@required this.localGitRepoPath,
@required this.remoteGitRepoPath,
@required this.gitBaseDirectory,
@required this.child, @required this.child,
}); });
@ -38,42 +38,56 @@ class StateContainer extends StatefulWidget {
@override @override
State<StatefulWidget> createState() { State<StatefulWidget> createState() {
return StateContainerState(this.onBoardingCompleted); var st = StateContainerState();
st.appState.localGitRepoConfigured = localGitRepoConfigured;
st.appState.remoteGitRepoConfigured = remoteGitRepoConfigured;
st.appState.localGitRepoPath = localGitRepoPath;
st.appState.remoteGitRepoPath = remoteGitRepoPath;
st.appState.gitBaseDirectory = gitBaseDirectory;
return st;
} }
} }
class StateContainerState extends State<StateContainer> { class StateContainerState extends State<StateContainer> {
AppState appState = AppState(); AppState appState = AppState();
NoteRepository noteRepo;
NoteRepository noteRepo = new GitNoteRepository(
getDirectory: getNotesDir,
dirName: "journal",
gitCloneUrl: "root@bcn.vhanda.in:git/test",
);
StateContainerState(bool onBoardingCompleted) {
appState.onBoardingCompleted = onBoardingCompleted;
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (appState.onBoardingCompleted) { assert(appState.localGitRepoConfigured);
_loadNotesFromDisk();
_syncNotes(); if (appState.remoteGitRepoConfigured) {
} else { noteRepo = new GitNoteRepository(
removeExistingClone(); baseDirectory: appState.gitBaseDirectory,
} dirName: appState.remoteGitRepoPath,
);
} else if (appState.localGitRepoConfigured) {
noteRepo = new GitNoteRepository(
baseDirectory: appState.gitBaseDirectory,
dirName: appState.localGitRepoPath,
);
} }
void removeExistingClone() async { // Just a fail safe
var baseDir = await getNotesDir(); if (!appState.remoteGitRepoConfigured) {
var dotGitDir = new Directory(p.join(baseDir.path, ".git")); removeExistingRemoteClone();
}
_loadNotesFromDisk();
_syncNotes();
}
void removeExistingRemoteClone() async {
var remoteGitDir = new Directory(
p.join(appState.gitBaseDirectory, appState.remoteGitRepoPath));
var dotGitDir = new Directory(p.join(remoteGitDir.path, ".git"));
bool exists = await dotGitDir.exists(); bool exists = await dotGitDir.exists();
if (exists) { if (exists) {
await baseDir.delete(recursive: true); await remoteGitDir.delete(recursive: true);
await baseDir.create();
} }
} }
@ -95,6 +109,11 @@ class StateContainerState extends State<StateContainer> {
} }
Future syncNotes() async { Future syncNotes() async {
if (!appState.remoteGitRepoConfigured) {
print("Not syncing because RemoteRepo not configured");
return true;
}
await noteRepo.sync(); await noteRepo.sync();
try { try {
@ -116,7 +135,12 @@ class StateContainerState extends State<StateContainer> {
} }
void _syncNotes() { void _syncNotes() {
print("Starting to syncNOtes"); if (!appState.remoteGitRepoConfigured) {
print("Not syncing because RemoteRepo not configured");
return;
}
print("Starting to syncNotes");
this.noteRepo.sync().then((loaded) { this.noteRepo.sync().then((loaded) {
print("NotesRepo Synced: " + loaded.toString()); print("NotesRepo Synced: " + loaded.toString());
_loadNotesFromDisk(); _loadNotesFromDisk();
@ -162,7 +186,7 @@ class StateContainerState extends State<StateContainer> {
void completeOnBoarding() { void completeOnBoarding() {
setState(() { setState(() {
this.appState.onBoardingCompleted = true; //this.appState.onBoardingCompleted = true;
_persistOnBoardingCompleted(); _persistOnBoardingCompleted();
_loadNotesFromDisk(); _loadNotesFromDisk();
@ -175,6 +199,13 @@ class StateContainerState extends State<StateContainer> {
pref.setBool("onBoardingCompleted", true); pref.setBool("onBoardingCompleted", true);
} }
Future _persistConfig() async {
var pref = await SharedPreferences.getInstance();
await pref.setBool(
"remoteGitRepoConfigured", appState.remoteGitRepoConfigured);
await pref.setString("remoteGitRepoPath", appState.remoteGitRepoPath);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _InheritedStateContainer( return _InheritedStateContainer(

View File

@ -12,17 +12,21 @@ typedef String NoteFileNameGenerator(Note note);
/// Each Note is saved in a different file /// Each Note is saved in a different file
/// Each note must have a fileName which ends in a .md /// Each note must have a fileName which ends in a .md
class FileStorage implements NoteRepository { class FileStorage implements NoteRepository {
final Future<Directory> Function() getDirectory; final String baseDirectory;
final NoteSerializer noteSerializer; final NoteSerializer noteSerializer;
const FileStorage({ FileStorage({
@required this.getDirectory, @required this.baseDirectory,
@required this.noteSerializer, @required this.noteSerializer,
}); }) {
assert(this.baseDirectory != null);
assert(this.baseDirectory.isNotEmpty);
print("FileStorage Directory: " + this.baseDirectory);
}
@override @override
Future<List<Note>> listNotes() async { Future<List<Note>> listNotes() async {
final dir = await getDirectory(); final dir = new Directory(baseDirectory);
var notes = new List<Note>(); var notes = new List<Note>();
var lister = dir.list(recursive: false); var lister = dir.list(recursive: false);
@ -56,8 +60,8 @@ class FileStorage implements NoteRepository {
@override @override
Future<NoteRepoResult> addNote(Note note) async { Future<NoteRepoResult> addNote(Note note) async {
final dir = await getDirectory(); var filePath = p.join(baseDirectory, note.fileName);
var filePath = p.join(dir.path, note.fileName); print("FileStorage: Adding note in " + filePath);
var file = new File(filePath); var file = new File(filePath);
if (file == null) { if (file == null) {
@ -71,8 +75,7 @@ class FileStorage implements NoteRepository {
@override @override
Future<NoteRepoResult> removeNote(Note note) async { Future<NoteRepoResult> removeNote(Note note) async {
final dir = await getDirectory(); var filePath = p.join(baseDirectory, note.fileName);
var filePath = p.join(dir.path, note.fileName);
var file = new File(filePath); var file = new File(filePath);
await file.delete(); await file.delete();
@ -91,7 +94,7 @@ class FileStorage implements NoteRepository {
} }
Future<Directory> saveNotes(List<Note> notes) async { Future<Directory> saveNotes(List<Note> notes) async {
final dir = await getDirectory(); final dir = new Directory(baseDirectory);
for (var note in notes) { for (var note in notes) {
var filePath = p.join(dir.path, note.fileName); var filePath = p.join(dir.path, note.fileName);

View File

@ -147,3 +147,16 @@ Future gitCommit({
print("gitCommit Failed: '${e.message}'."); print("gitCommit Failed: '${e.message}'.");
} }
} }
Future gitInit(String folderName) async {
print("Going to git init");
try {
await _platform.invokeMethod('gitInit', {
'folderName': folderName,
});
print("Done");
} on PlatformException catch (e) {
print("gitInit Failed: '${e.message}'.");
throw createGitException(e.message);
}
}

View File

@ -12,21 +12,18 @@ import 'package:journal/storage/notes_repository.dart';
class GitNoteRepository implements NoteRepository { class GitNoteRepository implements NoteRepository {
final FileStorage _fileStorage; final FileStorage _fileStorage;
final String gitCloneUrl; final String gitCloneUrl = "";
final String dirName; final String dirName;
bool cloned = false; bool cloned = false;
bool checkForCloned = false; bool checkForCloned = false;
final Future<Directory> Function() getDirectory;
GitNoteRepository({ GitNoteRepository({
@required this.gitCloneUrl,
@required this.dirName, @required this.dirName,
@required this.getDirectory, @required String baseDirectory,
}) : _fileStorage = FileStorage( }) : _fileStorage = FileStorage(
noteSerializer: new MarkdownYAMLSerializer(), noteSerializer: new MarkdownYAMLSerializer(),
getDirectory: getDirectory, baseDirectory: p.join(baseDirectory, dirName),
); );
@override @override
@ -40,8 +37,8 @@ class GitNoteRepository implements NoteRepository {
return result; return result;
} }
var baseDir = await this.getDirectory(); var baseDir = _fileStorage.baseDirectory;
var filePath = result.noteFilePath.replaceFirst(baseDir.path + "/", ""); var filePath = result.noteFilePath.replaceFirst(baseDir + "/", "");
await gitAdd(this.dirName, filePath); await gitAdd(this.dirName, filePath);
await gitCommit( await gitCommit(
@ -61,8 +58,9 @@ class GitNoteRepository implements NoteRepository {
return result; return result;
} }
var baseDir = await this.getDirectory(); // FIXME: '/' is not valid on all platforms
var filePath = result.noteFilePath.replaceFirst(baseDir.path + "/", ""); var baseDir = _fileStorage.baseDirectory;
var filePath = result.noteFilePath.replaceFirst(baseDir + "/", "");
await gitRm(this.dirName, filePath); await gitRm(this.dirName, filePath);
await gitCommit( await gitCommit(
@ -87,13 +85,19 @@ class GitNoteRepository implements NoteRepository {
@override @override
Future<bool> sync() async { Future<bool> sync() async {
print("Starting Sync"); if (gitCloneUrl == null || gitCloneUrl.isEmpty) {
print("Cannot sync because of lack of clone url");
return false;
}
if (!checkForCloned) { if (!checkForCloned) {
var baseDir = await this.getDirectory(); var baseDir = new Directory(_fileStorage.baseDirectory);
var dotGitDir = new Directory(p.join(baseDir.path, ".git")); var dotGitDir = new Directory(p.join(baseDir.path, ".git"));
cloned = await dotGitDir.exists(); cloned = await dotGitDir.exists();
checkForCloned = true; checkForCloned = true;
} }
// FIXME: If we are calling sync, it should always be cloned!
assert(cloned == true);
if (!cloned) { if (!cloned) {
await gitClone(this.gitCloneUrl, this.dirName); await gitClone(this.gitCloneUrl, this.dirName);
cloned = true; cloned = true;

View File

@ -1,8 +1,23 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:journal/state_container.dart';
class AppDrawer extends StatelessWidget { class AppDrawer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget setupGitButton = new Container();
var appState = StateContainer.of(context).appState;
if (!appState.remoteGitRepoConfigured) {
setupGitButton = ListTile(
title: new Text('Setup Git Sync'),
onTap: () {
Navigator.pop(context);
// Update the state of the app
// ...
},
);
}
return new Drawer( return new Drawer(
child: new ListView( child: new ListView(
// Important: Remove any padding from the ListView. // Important: Remove any padding from the ListView.
@ -23,6 +38,7 @@ class AppDrawer extends StatelessWidget {
), ),
), ),
), ),
setupGitButton,
new ListTile( new ListTile(
title: new Text('Share App'), title: new Text('Share App'),
onTap: () { onTap: () {

View File

@ -19,15 +19,19 @@ main() {
Note(fileName: "2.md", body: "test2", created: nowWithoutMicro()), Note(fileName: "2.md", body: "test2", created: nowWithoutMicro()),
]; ];
final directory = Directory.systemTemp.createTemp('__storage_test__'); Directory tempDir;
final storage = FileStorage( FileStorage storage;
getDirectory: () => directory,
setUpAll(() async {
tempDir = await Directory.systemTemp.createTemp('__storage_test__');
storage = FileStorage(
baseDirectory: tempDir.path,
noteSerializer: new JsonNoteSerializer(), noteSerializer: new JsonNoteSerializer(),
); );
});
tearDownAll(() async { tearDownAll(() async {
final tempDirectory = await directory; tempDir.deleteSync(recursive: true);
tempDirectory.deleteSync(recursive: true);
}); });
test('Should persist Notes to disk', () async { test('Should persist Notes to disk', () async {