diff --git a/android/app/src/main/java/io/gitjournal/gitjournal/GitInitTask.java b/android/app/src/main/java/io/gitjournal/gitjournal/GitInitTask.java new file mode 100644 index 00000000..495e39d2 --- /dev/null +++ b/android/app/src/main/java/io/gitjournal/gitjournal/GitInitTask.java @@ -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 { + 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; + } +} diff --git a/android/app/src/main/java/io/gitjournal/gitjournal/MainActivity.java b/android/app/src/main/java/io/gitjournal/gitjournal/MainActivity.java index 5bb8792a..5da177b8 100644 --- a/android/app/src/main/java/io/gitjournal/gitjournal/MainActivity.java +++ b/android/app/src/main/java/io/gitjournal/gitjournal/MainActivity.java @@ -146,6 +146,18 @@ public class MainActivity extends FlutterActivity { new GitCommitTask(result).execute(cloneLocation, authorName, authorEmail, message); 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")) { String comment = call.argument("comment"); if (comment == null || comment.isEmpty()) { diff --git a/lib/app.dart b/lib/app.dart index 9842f93e..683312c6 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:journal/state_container.dart'; import 'package:journal/screens/home_screen.dart'; -import 'package:journal/screens/onboarding_screens.dart'; import 'package:journal/screens/settings_screen.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; @@ -32,14 +30,7 @@ class JournalApp extends StatelessWidget { navigatorObservers: [JournalApp.observer], initialRoute: '/', routes: { - '/': (context) { - final stateContainer = StateContainer.of(context); - var onBoardingDone = stateContainer.appState.onBoardingCompleted; - var home = onBoardingDone - ? new HomeScreen() - : new OnBoardingScreen(stateContainer.completeOnBoarding); - return home; - }, + '/': (context) => HomeScreen(), '/settings': (context) => SettingsScreen(), }, ); diff --git a/lib/appstate.dart b/lib/appstate.dart index 8fe3dbdd..0a753413 100644 --- a/lib/appstate.dart +++ b/lib/appstate.dart @@ -1,12 +1,21 @@ import 'package:journal/note.dart'; 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; List notes; AppState({ - this.onBoardingCompleted = false, this.isLoadingFromDisk = false, this.notes = const [], }); diff --git a/lib/main.dart b/lib/main.dart index 478804a3..0428c518 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter_crashlytics/flutter_crashlytics.dart'; import 'package:journal/app.dart'; import 'package:journal/state_container.dart'; +import 'package:journal/storage/git.dart'; void main() async { bool isInDebugMode = true; @@ -30,7 +31,11 @@ void main() async { Future runJournalApp() async { 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.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( - onBoardingCompleted: onBoardingCompleted, + localGitRepoConfigured: localGitRepoConfigured, + remoteGitRepoConfigured: remoteGitRepoConfigured, + localGitRepoPath: localGitRepoPath, + remoteGitRepoPath: remoteGitRepoPath, + gitBaseDirectory: dir.path, child: JournalApp(), )); } diff --git a/lib/screens/onboarding_screens.dart b/lib/screens/onboarding_screens.dart index 005fed42..927cad71 100644 --- a/lib/screens/onboarding_screens.dart +++ b/lib/screens/onboarding_screens.dart @@ -300,6 +300,8 @@ class OnBoardingScreenState extends State { } Future _removeExistingClone() async { + // FIXME: Implement this + /* var baseDir = await getNotesDir(); var dotGitDir = new Directory(p.join(baseDir.path, ".git")); bool exists = await dotGitDir.exists(); @@ -307,6 +309,7 @@ class OnBoardingScreenState extends State { await baseDir.delete(recursive: true); await baseDir.create(); } + */ } } diff --git a/lib/state_container.dart b/lib/state_container.dart index 20077d7b..c7a40d31 100644 --- a/lib/state_container.dart +++ b/lib/state_container.dart @@ -13,20 +13,20 @@ import 'package:journal/storage/git_storage.dart'; import 'package:journal/storage/git.dart'; import 'package:journal/datetime_utils.dart'; -Future getNotesDir() async { - var appDir = await getGitBaseDirectory(); - var dir = new Directory(p.join(appDir.path, "journal")); - await dir.create(); - - return dir; -} - class StateContainer extends StatefulWidget { final Widget child; - final bool onBoardingCompleted; + final bool localGitRepoConfigured; + final bool remoteGitRepoConfigured; + final String localGitRepoPath; + final String remoteGitRepoPath; + final String gitBaseDirectory; StateContainer({ - @required this.onBoardingCompleted, + @required this.localGitRepoConfigured, + @required this.remoteGitRepoConfigured, + @required this.localGitRepoPath, + @required this.remoteGitRepoPath, + @required this.gitBaseDirectory, @required this.child, }); @@ -38,42 +38,56 @@ class StateContainer extends StatefulWidget { @override State 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 { AppState appState = AppState(); - - NoteRepository noteRepo = new GitNoteRepository( - getDirectory: getNotesDir, - dirName: "journal", - gitCloneUrl: "root@bcn.vhanda.in:git/test", - ); - - StateContainerState(bool onBoardingCompleted) { - appState.onBoardingCompleted = onBoardingCompleted; - } + NoteRepository noteRepo; @override void initState() { super.initState(); - if (appState.onBoardingCompleted) { - _loadNotesFromDisk(); - _syncNotes(); - } else { - removeExistingClone(); + assert(appState.localGitRepoConfigured); + + if (appState.remoteGitRepoConfigured) { + noteRepo = new GitNoteRepository( + baseDirectory: appState.gitBaseDirectory, + dirName: appState.remoteGitRepoPath, + ); + } else if (appState.localGitRepoConfigured) { + noteRepo = new GitNoteRepository( + baseDirectory: appState.gitBaseDirectory, + dirName: appState.localGitRepoPath, + ); } + + // Just a fail safe + if (!appState.remoteGitRepoConfigured) { + removeExistingRemoteClone(); + } + + _loadNotesFromDisk(); + _syncNotes(); } - void removeExistingClone() async { - var baseDir = await getNotesDir(); - var dotGitDir = new Directory(p.join(baseDir.path, ".git")); + 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(); if (exists) { - await baseDir.delete(recursive: true); - await baseDir.create(); + await remoteGitDir.delete(recursive: true); } } @@ -95,6 +109,11 @@ class StateContainerState extends State { } Future syncNotes() async { + if (!appState.remoteGitRepoConfigured) { + print("Not syncing because RemoteRepo not configured"); + return true; + } + await noteRepo.sync(); try { @@ -116,7 +135,12 @@ class StateContainerState extends State { } 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) { print("NotesRepo Synced: " + loaded.toString()); _loadNotesFromDisk(); @@ -162,7 +186,7 @@ class StateContainerState extends State { void completeOnBoarding() { setState(() { - this.appState.onBoardingCompleted = true; + //this.appState.onBoardingCompleted = true; _persistOnBoardingCompleted(); _loadNotesFromDisk(); @@ -175,6 +199,13 @@ class StateContainerState extends State { 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 Widget build(BuildContext context) { return _InheritedStateContainer( diff --git a/lib/storage/file_storage.dart b/lib/storage/file_storage.dart index 3d6b02f0..18c8ba25 100644 --- a/lib/storage/file_storage.dart +++ b/lib/storage/file_storage.dart @@ -12,17 +12,21 @@ typedef String NoteFileNameGenerator(Note note); /// Each Note is saved in a different file /// Each note must have a fileName which ends in a .md class FileStorage implements NoteRepository { - final Future Function() getDirectory; + final String baseDirectory; final NoteSerializer noteSerializer; - const FileStorage({ - @required this.getDirectory, + FileStorage({ + @required this.baseDirectory, @required this.noteSerializer, - }); + }) { + assert(this.baseDirectory != null); + assert(this.baseDirectory.isNotEmpty); + print("FileStorage Directory: " + this.baseDirectory); + } @override Future> listNotes() async { - final dir = await getDirectory(); + final dir = new Directory(baseDirectory); var notes = new List(); var lister = dir.list(recursive: false); @@ -56,8 +60,8 @@ class FileStorage implements NoteRepository { @override Future addNote(Note note) async { - final dir = await getDirectory(); - var filePath = p.join(dir.path, note.fileName); + var filePath = p.join(baseDirectory, note.fileName); + print("FileStorage: Adding note in " + filePath); var file = new File(filePath); if (file == null) { @@ -71,8 +75,7 @@ class FileStorage implements NoteRepository { @override Future removeNote(Note note) async { - final dir = await getDirectory(); - var filePath = p.join(dir.path, note.fileName); + var filePath = p.join(baseDirectory, note.fileName); var file = new File(filePath); await file.delete(); @@ -91,7 +94,7 @@ class FileStorage implements NoteRepository { } Future saveNotes(List notes) async { - final dir = await getDirectory(); + final dir = new Directory(baseDirectory); for (var note in notes) { var filePath = p.join(dir.path, note.fileName); diff --git a/lib/storage/git.dart b/lib/storage/git.dart index 27f5d249..53b2a41b 100644 --- a/lib/storage/git.dart +++ b/lib/storage/git.dart @@ -147,3 +147,16 @@ Future gitCommit({ 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); + } +} diff --git a/lib/storage/git_storage.dart b/lib/storage/git_storage.dart index cbd54074..79d6b988 100644 --- a/lib/storage/git_storage.dart +++ b/lib/storage/git_storage.dart @@ -12,21 +12,18 @@ import 'package:journal/storage/notes_repository.dart'; class GitNoteRepository implements NoteRepository { final FileStorage _fileStorage; - final String gitCloneUrl; + final String gitCloneUrl = ""; final String dirName; bool cloned = false; bool checkForCloned = false; - final Future Function() getDirectory; - GitNoteRepository({ - @required this.gitCloneUrl, @required this.dirName, - @required this.getDirectory, + @required String baseDirectory, }) : _fileStorage = FileStorage( noteSerializer: new MarkdownYAMLSerializer(), - getDirectory: getDirectory, + baseDirectory: p.join(baseDirectory, dirName), ); @override @@ -40,8 +37,8 @@ class GitNoteRepository implements NoteRepository { return result; } - var baseDir = await this.getDirectory(); - var filePath = result.noteFilePath.replaceFirst(baseDir.path + "/", ""); + var baseDir = _fileStorage.baseDirectory; + var filePath = result.noteFilePath.replaceFirst(baseDir + "/", ""); await gitAdd(this.dirName, filePath); await gitCommit( @@ -61,8 +58,9 @@ class GitNoteRepository implements NoteRepository { return result; } - var baseDir = await this.getDirectory(); - var filePath = result.noteFilePath.replaceFirst(baseDir.path + "/", ""); + // FIXME: '/' is not valid on all platforms + var baseDir = _fileStorage.baseDirectory; + var filePath = result.noteFilePath.replaceFirst(baseDir + "/", ""); await gitRm(this.dirName, filePath); await gitCommit( @@ -87,13 +85,19 @@ class GitNoteRepository implements NoteRepository { @override Future sync() async { - print("Starting Sync"); + if (gitCloneUrl == null || gitCloneUrl.isEmpty) { + print("Cannot sync because of lack of clone url"); + return false; + } + if (!checkForCloned) { - var baseDir = await this.getDirectory(); + var baseDir = new Directory(_fileStorage.baseDirectory); var dotGitDir = new Directory(p.join(baseDir.path, ".git")); cloned = await dotGitDir.exists(); checkForCloned = true; } + // FIXME: If we are calling sync, it should always be cloned! + assert(cloned == true); if (!cloned) { await gitClone(this.gitCloneUrl, this.dirName); cloned = true; diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart index 18decf6b..889171e7 100644 --- a/lib/widgets/app_drawer.dart +++ b/lib/widgets/app_drawer.dart @@ -1,8 +1,23 @@ import 'package:flutter/material.dart'; +import 'package:journal/state_container.dart'; class AppDrawer extends StatelessWidget { @override 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( child: new ListView( // Important: Remove any padding from the ListView. @@ -23,6 +38,7 @@ class AppDrawer extends StatelessWidget { ), ), ), + setupGitButton, new ListTile( title: new Text('Share App'), onTap: () { diff --git a/test/file_storage_test.dart b/test/file_storage_test.dart index a7107575..bf814c42 100644 --- a/test/file_storage_test.dart +++ b/test/file_storage_test.dart @@ -19,15 +19,19 @@ main() { Note(fileName: "2.md", body: "test2", created: nowWithoutMicro()), ]; - final directory = Directory.systemTemp.createTemp('__storage_test__'); - final storage = FileStorage( - getDirectory: () => directory, - noteSerializer: new JsonNoteSerializer(), - ); + Directory tempDir; + FileStorage storage; + + setUpAll(() async { + tempDir = await Directory.systemTemp.createTemp('__storage_test__'); + storage = FileStorage( + baseDirectory: tempDir.path, + noteSerializer: new JsonNoteSerializer(), + ); + }); tearDownAll(() async { - final tempDirectory = await directory; - tempDirectory.deleteSync(recursive: true); + tempDir.deleteSync(recursive: true); }); test('Should persist Notes to disk', () async {