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.material.icons.extended)
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.ui.tooling.preview)
implementation(libs.protolite.well.known.types)
runtimeOnly(libs.androidx.ui.tooling) runtimeOnly(libs.androidx.ui.tooling)
implementation(libs.org.eclipse.jgit) implementation(libs.org.eclipse.jgit)
@ -63,5 +64,7 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-tooling")
implementation("androidx.compose.ui:ui-tooling-preview") 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 import android.content.Context
class AppContext( class AppContext(
val androidContext: Context val androidContext: Context,
) { ) {
val gitActions = GitActions(androidContext) 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( class GitActions(
private val context: Context 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 myCommitter = "Mobile Sandbox" to "example@example.org"
private val credentials = UsernamePasswordCredentialsProvider( private val credentials = UsernamePasswordCredentialsProvider(
"popequer-mobile-sandbox", "popequer-mobile-sandbox",
@ -42,9 +42,13 @@ class GitActions(
fun sync() { fun sync() {
git.fetch().setCredentialsProvider(credentials).call() git.fetch().setCredentialsProvider(credentials).call()
git.rebase().setUpstream("origin/master").call().also { result -> git.reset().setMode(ResetCommand.ResetType.HARD).setRef("origin/master").call().also {
Log.i("GitActions", "Rebase status: ${result.status} ${result.conflicts}") 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") Log.i("GitActions", "Successfully sync repo")
} }
fun trigger() { 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.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -84,6 +85,13 @@ fun AppContainer(
} }
}, },
actions = { actions = {
IconButton(onClick = {
scope.launch(Dispatchers.IO) {
context.backgroundActions.refresh()
}
}) {
Icon(Icons.Filled.Refresh, contentDescription = "Refresh icon")
}
IconButton(onClick = { IconButton(onClick = {
// TODO: switch to settings view // TODO: switch to settings view
}) { }) {
@ -99,7 +107,7 @@ fun AppContainer(
onClick = { onClick = {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
runCatching { runCatching {
context.gitActions.trigger() // context.gitActions.trigger()
}.onFailure { }.onFailure {
Log.e("MainActivity", "Failed to trigger git actions", it) Log.e("MainActivity", "Failed to trigger git actions", it)
} }

View file

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

View file

@ -19,17 +19,17 @@ import net.mbess.popequer.Foo
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
System.loadLibrary("rustadapter")
val appContext = AppContext(this) 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) val foo_instance = Foo(10)
Log.d("MainActivity", "From rust: " + foo_instance.f(32, 43)) Log.d("MainActivity", "From rust: " + foo_instance.f(32, 43))
foo_instance.setField(154) foo_instance.setField(154)
Log.d("MainActivity", "${foo_instance.data}") Log.d("MainActivity", "${foo_instance.data}")
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
Looper.prepare() Looper.prepare()
runCatching { runCatching {
@ -40,6 +40,7 @@ class MainActivity : ComponentActivity() {
Toast.makeText(appContext.androidContext, "A repo was just cloned", Toast.LENGTH_LONG) Toast.makeText(appContext.androidContext, "A repo was just cloned", Toast.LENGTH_LONG)
.show() .show()
} }
appContext.backgroundActions.refresh()
}.onFailure { }.onFailure {
Log.e("MainActivity", "Failed to prepare git repo", it) Log.e("MainActivity", "Failed to prepare git repo", it)
} }

View file

@ -1,9 +1,46 @@
package net.mbess.popequer.ui 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.material3.Text
import androidx.compose.runtime.Composable 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 @Composable
fun UpcomingEventsRoute() { fun UpcomingEventsRoute(
Text(text = "This is the upcoming events route") 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" material3 = "1.1.2"
org-eclipse-jgit = "6.8.0.202311291450-r" org-eclipse-jgit = "6.8.0.202311291450-r"
twain = "0.2.2" twain = "0.2.2"
protolite-well-known-types = "18.0.0"
[libraries] [libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } 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" } 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" } org-eclipse-jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version.ref = "org-eclipse-jgit" }
twain = { module = "com.meetup:twain", version.ref = "twain" } 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] [plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" } androidApplication = { id = "com.android.application", version.ref = "agp" }

View file

@ -13,6 +13,8 @@ rifgen = "0.1"
popequer_rust = { path = "../../../popequer_rust" } popequer_rust = { path = "../../../popequer_rust" }
jni-sys = "0.3" jni-sys = "0.3"
log = "0.4.20" log = "0.4.20"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[build-dependencies] [build-dependencies]
rifgen = "0.1" rifgen = "0.1"

View file

@ -2,6 +2,8 @@
use std::env; use std::env;
fn main() { 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 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"; 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) rifgen::Generator::new(rifgen::TypeCases::CamelCase, rifgen::Language::Java,source_folder)
@ -18,7 +20,7 @@
.join("popequer"), .join("popequer"),
"net.mbess.popequer".into(), "net.mbess.popequer".into(),
) )
.use_null_annotation_from_package("android.support.annotation".into()), .use_null_annotation_from_package("androidx.annotation".into()),
)) ))
.rustfmt_bindings(true); .rustfmt_bindings(true);

View file

@ -1,6 +1,13 @@
//Automatically generated by rifgen //Automatically generated by rifgen
use crate::*; use crate::*;
use jni_sys::*; use jni_sys::*;
foreign_enum!(
enum PopequerAdapterErr {
ReadDb = PopequerAdapterErr::ReadDb,
Indexing = PopequerAdapterErr::Indexing,
View = PopequerAdapterErr::View,
}
);
foreign_class!( foreign_class!(
class Foo { class Foo {
self_type Foo; self_type Foo;
@ -11,3 +18,11 @@ foreign_class!(
fn Foo::set_field(& mut self , v : i32); alias setField; 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 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 { struct Foo {
data: i32 data: i32
} }