Compare commits

..

2 commits

20 changed files with 320 additions and 23 deletions

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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");
}
}
}

View file

@ -0,0 +1,11 @@
package net.mbess.popequer
import java.time.Instant
class EventItem(
val name: String,
val startTime: Instant,
val endTime: Instant?
) {
}

View file

@ -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() {

View file

@ -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<EventItem> {
val events_str = adapter.upcomingEvents()
Log.d("Popequer", "From rust: $events_str")
val events = PopequerDeserializer().deserializeEvents(events_str)
return events;
}
}

View file

@ -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;
}

View file

@ -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);
}
}
}

View file

@ -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<EventItem> {
val listType: Type = object : TypeToken<ArrayList<EventItem>?>() {}.type
return gsonInstance
.fromJson(inp, listType)
}
}

View file

@ -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
) {
}

View file

@ -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)
}

View file

@ -28,7 +28,7 @@ fun AppNavGraph(
composable(
route = AppDestinations.UPCOMING_EVENTS
) {
UpcomingEventsRoute()
UpcomingEventsRoute(appContext)
}
}
}

View file

@ -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)
}

View file

@ -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)}")
}
}
}
}
}
}

View file

@ -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<Instant?> {
@Throws(JsonParseException::class)
override fun deserialize(
json: JsonElement,
typeOfT: Type?,
context: JsonDeserializationContext?
): Instant?
{
if (json.isJsonNull) {
return null
}
return Instant.parse(json.asString)
}
}

View file

@ -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" }

View file

@ -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"

View file

@ -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);

View file

@ -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;
}
);

View file

@ -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<String>
}
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(&notebook) {
Ok(x) => x,
Err(err) => {
return format!("Err: {err:?}");
}
};
let formatted_events: Vec<EventItem> = 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
}