diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ecf9f76..690369f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { implementation(libs.androidx.material.icons.extended) implementation(libs.androidx.material3) implementation(libs.androidx.ui.tooling.preview) + implementation(libs.protolite.well.known.types) runtimeOnly(libs.androidx.ui.tooling) implementation(libs.org.eclipse.jgit) @@ -63,5 +64,7 @@ dependencies { debugImplementation("androidx.compose.ui:ui-tooling") implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.annotation:annotation") + implementation("com.google.code.gson:gson:2.10.1") } diff --git a/app/src/main/java/net/mbess/popequer/AppContext.kt b/app/src/main/java/net/mbess/popequer/AppContext.kt index d9f1c26..d8e4139 100644 --- a/app/src/main/java/net/mbess/popequer/AppContext.kt +++ b/app/src/main/java/net/mbess/popequer/AppContext.kt @@ -3,7 +3,9 @@ package net.mbess.popequer import android.content.Context class AppContext( - val androidContext: Context + val androidContext: Context, ) { val gitActions = GitActions(androidContext) + val popequer = Popequer(PopequerAdapter(gitActions.cloneFolder.absolutePath)) + val backgroundActions = BackgroundActions(this) } \ No newline at end of file diff --git a/app/src/main/java/net/mbess/popequer/BackgroundActions.kt b/app/src/main/java/net/mbess/popequer/BackgroundActions.kt new file mode 100644 index 0000000..69ed442 --- /dev/null +++ b/app/src/main/java/net/mbess/popequer/BackgroundActions.kt @@ -0,0 +1,18 @@ +package net.mbess.popequer + +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class BackgroundActions(val context: AppContext) { + fun refresh() { + runCatching { + context.gitActions.sync() + context.popequer.index() + }.onFailure { + Log.e("BackgroundActions", "Failed to refresh the Notebook"); + }.onSuccess { + Log.i("BackgroundActions", "Notebook refresh successfully"); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mbess/popequer/EventItem.kt b/app/src/main/java/net/mbess/popequer/EventItem.kt new file mode 100644 index 0000000..7effad0 --- /dev/null +++ b/app/src/main/java/net/mbess/popequer/EventItem.kt @@ -0,0 +1,11 @@ +package net.mbess.popequer + +import java.time.Instant + +class EventItem( + val name: String, + val startTime: Instant, + val endTime: Instant? +) { + +} \ No newline at end of file diff --git a/app/src/main/java/net/mbess/popequer/GitActions.kt b/app/src/main/java/net/mbess/popequer/GitActions.kt index 26eb491..f1c289e 100644 --- a/app/src/main/java/net/mbess/popequer/GitActions.kt +++ b/app/src/main/java/net/mbess/popequer/GitActions.kt @@ -11,7 +11,7 @@ import kotlin.math.absoluteValue class GitActions( private val context: Context ) { - private val cloneFolder = context.filesDir.resolve("popequer") + val cloneFolder = context.filesDir.resolve("popequer") private val myCommitter = "Mobile Sandbox" to "example@example.org" private val credentials = UsernamePasswordCredentialsProvider( "popequer-mobile-sandbox", @@ -42,9 +42,13 @@ class GitActions( fun sync() { git.fetch().setCredentialsProvider(credentials).call() - git.rebase().setUpstream("origin/master").call().also { result -> - Log.i("GitActions", "Rebase status: ${result.status} ${result.conflicts}") + git.reset().setMode(ResetCommand.ResetType.HARD).setRef("origin/master").call().also { + result -> + Log.i("GitActions", "reset!"); } + //git.rebase().setUpstream("origin/master").call().also { result -> + // Log.i("GitActions", "Rebase status: ${result.status} ${result.conflicts}") + //} Log.i("GitActions", "Successfully sync repo") } fun trigger() { diff --git a/app/src/main/java/net/mbess/popequer/Popequer.kt b/app/src/main/java/net/mbess/popequer/Popequer.kt new file mode 100644 index 0000000..36080a7 --- /dev/null +++ b/app/src/main/java/net/mbess/popequer/Popequer.kt @@ -0,0 +1,18 @@ +package net.mbess.popequer + +import android.util.Log + +class Popequer( + val adapter: PopequerAdapter +) { + + fun index(): String { + return adapter.index() + } + fun upcomingEvents(): List { + val events_str = adapter.upcomingEvents() + Log.d("Popequer", "From rust: $events_str") + val events = PopequerDeserializer().deserializeEvents(events_str) + return events; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mbess/popequer/PopequerAdapter.java b/app/src/main/java/net/mbess/popequer/PopequerAdapter.java new file mode 100644 index 0000000..d89ebaa --- /dev/null +++ b/app/src/main/java/net/mbess/popequer/PopequerAdapter.java @@ -0,0 +1,47 @@ +// Automatically generated by flapigen +package net.mbess.popequer; +import androidx.annotation.NonNull; + +public final class PopequerAdapter { + + public PopequerAdapter(@NonNull String notebook_fs_path) { + mNativeObj = init(notebook_fs_path); + } + private static native long init(@NonNull String notebook_fs_path); + + public final @NonNull String index() { + String ret = do_index(mNativeObj); + + return ret; + } + private static native @NonNull String do_index(long self); + + public final @NonNull String upcomingEvents() { + String ret = do_upcomingEvents(mNativeObj); + + return ret; + } + private static native @NonNull String do_upcomingEvents(long self); + + public synchronized void delete() { + if (mNativeObj != 0) { + do_delete(mNativeObj); + mNativeObj = 0; + } + } + @Override + protected void finalize() throws Throwable { + try { + delete(); + } + finally { + super.finalize(); + } + } + private static native void do_delete(long me); + /*package*/ PopequerAdapter(InternalPointerMarker marker, long ptr) { + assert marker == InternalPointerMarker.RAW_PTR; + this.mNativeObj = ptr; + } + /*package*/ long mNativeObj; +} \ No newline at end of file diff --git a/app/src/main/java/net/mbess/popequer/PopequerAdapterErr.java b/app/src/main/java/net/mbess/popequer/PopequerAdapterErr.java new file mode 100644 index 0000000..c9a80f5 --- /dev/null +++ b/app/src/main/java/net/mbess/popequer/PopequerAdapterErr.java @@ -0,0 +1,23 @@ +// Automatically generated by flapigen +package net.mbess.popequer; + + +public enum PopequerAdapterErr { + ReadDb(0), + Indexing(1), + View(2); + + private final int value; + PopequerAdapterErr(int value) { + this.value = value; + } + public final int getValue() { return value; } + /*package*/ static PopequerAdapterErr fromInt(int x) { + switch (x) { + case 0: return ReadDb; + case 1: return Indexing; + case 2: return View; + default: throw new Error("Invalid value for enum PopequerAdapterErr: " + x); + } + } +} diff --git a/app/src/main/java/net/mbess/popequer/PopequerDeserializer.kt b/app/src/main/java/net/mbess/popequer/PopequerDeserializer.kt new file mode 100644 index 0000000..6accde8 --- /dev/null +++ b/app/src/main/java/net/mbess/popequer/PopequerDeserializer.kt @@ -0,0 +1,22 @@ +package net.mbess.popequer + +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.util.ArrayList + +import com.google.gson.reflect.TypeToken +import java.lang.reflect.Type +import java.time.Instant + + +class PopequerDeserializer() { + private var gsonInstance: Gson = + GsonBuilder().registerTypeAdapter(Instant::class.java, InstantDeserializer()).create() + + fun deserializeEvents(inp: String): List { + val listType: Type = object : TypeToken?>() {}.type + return gsonInstance + .fromJson(inp, listType) + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/mbess/popequer/PopequerProvider.kt b/app/src/main/java/net/mbess/popequer/PopequerProvider.kt deleted file mode 100644 index 4d8d1b2..0000000 --- a/app/src/main/java/net/mbess/popequer/PopequerProvider.kt +++ /dev/null @@ -1,10 +0,0 @@ -package net.mbess.popequer - -import android.content.Context -import android.util.Log -import kotlin.math.absoluteValue - -class PopequerProvider( - private val context: Context -) { -} diff --git a/app/src/main/java/net/mbess/popequer/ui/App.kt b/app/src/main/java/net/mbess/popequer/ui/App.kt index a711283..c4ea8b7 100644 --- a/app/src/main/java/net/mbess/popequer/ui/App.kt +++ b/app/src/main/java/net/mbess/popequer/ui/App.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.* import androidx.compose.runtime.Composable @@ -84,6 +85,13 @@ fun AppContainer( } }, actions = { + IconButton(onClick = { + scope.launch(Dispatchers.IO) { + context.backgroundActions.refresh() + } + }) { + Icon(Icons.Filled.Refresh, contentDescription = "Refresh icon") + } IconButton(onClick = { // TODO: switch to settings view }) { @@ -99,7 +107,7 @@ fun AppContainer( onClick = { scope.launch(Dispatchers.IO) { runCatching { - context.gitActions.trigger() + // context.gitActions.trigger() }.onFailure { Log.e("MainActivity", "Failed to trigger git actions", it) } diff --git a/app/src/main/java/net/mbess/popequer/ui/AppNavGraph.kt b/app/src/main/java/net/mbess/popequer/ui/AppNavGraph.kt index d22ae56..ac931a6 100644 --- a/app/src/main/java/net/mbess/popequer/ui/AppNavGraph.kt +++ b/app/src/main/java/net/mbess/popequer/ui/AppNavGraph.kt @@ -28,7 +28,7 @@ fun AppNavGraph( composable( route = AppDestinations.UPCOMING_EVENTS ) { - UpcomingEventsRoute() + UpcomingEventsRoute(appContext) } } } \ No newline at end of file diff --git a/app/src/main/java/net/mbess/popequer/ui/MainActivity.kt b/app/src/main/java/net/mbess/popequer/ui/MainActivity.kt index e6764c4..1b2e329 100644 --- a/app/src/main/java/net/mbess/popequer/ui/MainActivity.kt +++ b/app/src/main/java/net/mbess/popequer/ui/MainActivity.kt @@ -19,17 +19,17 @@ import net.mbess.popequer.Foo class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + System.loadLibrary("rustadapter") val appContext = AppContext(this) - //Native.fsInfo(appContext.gitActions.cloneFolder.absolutePath).let { - // Log.d("MainActivity", "From foreign rust result fsInfo $it") - //} - System.loadLibrary("rustadapter") + + val foo_instance = Foo(10) Log.d("MainActivity", "From rust: " + foo_instance.f(32, 43)) foo_instance.setField(154) Log.d("MainActivity", "${foo_instance.data}") + CoroutineScope(Dispatchers.IO).launch { Looper.prepare() runCatching { @@ -40,6 +40,7 @@ class MainActivity : ComponentActivity() { Toast.makeText(appContext.androidContext, "A repo was just cloned", Toast.LENGTH_LONG) .show() } + appContext.backgroundActions.refresh() }.onFailure { Log.e("MainActivity", "Failed to prepare git repo", it) } diff --git a/app/src/main/java/net/mbess/popequer/ui/UpcomingEventsRoute.kt b/app/src/main/java/net/mbess/popequer/ui/UpcomingEventsRoute.kt index 0ccc7bc..394d76c 100644 --- a/app/src/main/java/net/mbess/popequer/ui/UpcomingEventsRoute.kt +++ b/app/src/main/java/net/mbess/popequer/ui/UpcomingEventsRoute.kt @@ -1,9 +1,46 @@ package net.mbess.popequer.ui +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import net.mbess.popequer.AppContext +import java.time.LocalDateTime +import java.time.ZoneOffset.UTC +import java.time.format.DateTimeFormatter @Composable -fun UpcomingEventsRoute() { - Text(text = "This is the upcoming events route") +fun UpcomingEventsRoute( + appContext: AppContext +) { + Surface(modifier = Modifier.padding(10.dp)) { + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + LazyColumn { + itemsIndexed(appContext.popequer.upcomingEvents()) { index, item -> + Card( + elevation = CardDefaults.cardElevation( + defaultElevation = 6.dp + ), + shape = MaterialTheme.shapes.medium, + modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp) + ) { + Column(modifier = Modifier.padding(15.dp)) { + Text(item.name, style = MaterialTheme.typography.headlineSmall) + Text("Start: ${LocalDateTime.ofInstant(item.startTime, UTC).format(formatter)}") + } + } + } + } + } } diff --git a/app/src/main/java/net/mbess/popequer/utils.kt b/app/src/main/java/net/mbess/popequer/utils.kt new file mode 100644 index 0000000..1b0e120 --- /dev/null +++ b/app/src/main/java/net/mbess/popequer/utils.kt @@ -0,0 +1,24 @@ +package net.mbess.popequer + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import java.lang.reflect.Type +import java.time.Instant + +class InstantDeserializer : JsonDeserializer { + @Throws(JsonParseException::class) + override fun deserialize( + json: JsonElement, + typeOfT: Type?, + context: JsonDeserializationContext? + ): Instant? + { + if (json.isJsonNull) { + return null + } + return Instant.parse(json.asString) + } +} + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7924575..63e237c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ compose-bom = "2023.10.01" material3 = "1.1.2" org-eclipse-jgit = "6.8.0.202311291450-r" twain = "0.2.2" +protolite-well-known-types = "18.0.0" [libraries] androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } @@ -27,6 +28,7 @@ compose-markdown = { module = "com.github.jeziellago:compose-markdown", version. kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines-android" } org-eclipse-jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version.ref = "org-eclipse-jgit" } twain = { module = "com.meetup:twain", version.ref = "twain" } +protolite-well-known-types = { group = "com.google.firebase", name = "protolite-well-known-types", version.ref = "protolite-well-known-types" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/rust-adapter/Cargo.toml b/rust-adapter/Cargo.toml index d94fc12..e8a8b96 100644 --- a/rust-adapter/Cargo.toml +++ b/rust-adapter/Cargo.toml @@ -13,6 +13,8 @@ rifgen = "0.1" popequer_rust = { path = "../../../popequer_rust" } jni-sys = "0.3" log = "0.4.20" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" [build-dependencies] rifgen = "0.1" diff --git a/rust-adapter/build.rs b/rust-adapter/build.rs index 0ac339f..0541e62 100644 --- a/rust-adapter/build.rs +++ b/rust-adapter/build.rs @@ -2,6 +2,8 @@ use std::env; fn main() { + env::set_var("RUST_LOG", "DEBUG"); + let source_folder = "/mnt/extramedia3/mbess/workspace/popequer/android_app/rust-adapter/src"; //use your projects folder let out_file = "/mnt/extramedia3/mbess/workspace/popequer/android_app/rust-adapter/glue.rs"; rifgen::Generator::new(rifgen::TypeCases::CamelCase, rifgen::Language::Java,source_folder) @@ -18,7 +20,7 @@ .join("popequer"), "net.mbess.popequer".into(), ) - .use_null_annotation_from_package("android.support.annotation".into()), + .use_null_annotation_from_package("androidx.annotation".into()), )) .rustfmt_bindings(true); diff --git a/rust-adapter/glue.rs b/rust-adapter/glue.rs index 3321dc9..6fd1832 100644 --- a/rust-adapter/glue.rs +++ b/rust-adapter/glue.rs @@ -1,6 +1,13 @@ //Automatically generated by rifgen use crate::*; use jni_sys::*; +foreign_enum!( + enum PopequerAdapterErr { + ReadDb = PopequerAdapterErr::ReadDb, + Indexing = PopequerAdapterErr::Indexing, + View = PopequerAdapterErr::View, + } +); foreign_class!( class Foo { self_type Foo; @@ -11,3 +18,11 @@ foreign_class!( fn Foo::set_field(& mut self , v : i32); alias setField; } ); +foreign_class!( + class PopequerAdapter { + self_type PopequerAdapter; + constructor PopequerAdapter::new(notebook_fs_path : String)->PopequerAdapter; + fn PopequerAdapter::index(& self)->String; alias index; + fn PopequerAdapter::upcoming_events(& self)->String; alias upcomingEvents; + } +); diff --git a/rust-adapter/src/lib.rs b/rust-adapter/src/lib.rs index 1964754..44b71cd 100644 --- a/rust-adapter/src/lib.rs +++ b/rust-adapter/src/lib.rs @@ -4,6 +4,74 @@ pub use crate::java_glue::*; use rifgen::rifgen_attr::*; +use popequer::{database::read::read_db, fs_notebook::NotebookContext, indexer::index_and_save, views::calendar::get_upcoming_events}; + +use serde::Serialize; + +struct PopequerAdapter { + context: NotebookContext +} + +#[generate_interface] +enum PopequerAdapterErr { + ReadDb, + Indexing, + View +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct EventItem { + name: String, + start_time: String, + end_time: Option +} + +impl PopequerAdapter { + #[generate_interface(constructor)] + fn new(notebook_fs_path: String) -> PopequerAdapter { + PopequerAdapter { + context: NotebookContext { + base_path: notebook_fs_path.into() + } + } + } + + #[generate_interface] + fn index(&self) -> String { + match index_and_save( + &self.context + ) { + Ok(_n) => format!("Indexing OK"), + Err(err) => format!("Indexing ERR {err:?}") + } + } + + #[generate_interface] + fn upcoming_events(&self) -> String { + let notebook = match read_db(&self.context) { + Ok(n) => n, + Err(err) => { + return format!("Err: {err:?}"); + } + }; + let events = match get_upcoming_events(¬ebook) { + Ok(x) => x, + Err(err) => { + return format!("Err: {err:?}"); + } + }; + let formatted_events: Vec = events.iter().map(|e| { + EventItem { + name: e.name.clone(), + start_time: e.start_time.to_rfc3339(), + end_time: e.end_time.map(|x| x.to_rfc3339()) + } + }).collect(); + serde_json::to_string(&formatted_events).expect("Cannot serialize into json") + } +} + struct Foo { data: i32 }