diff --git a/android/build.gradle b/android/build.gradle index c4e983d..00f44a8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -75,7 +75,7 @@ dependencies { exclude group: 'org.threeten', module: 'threetenbp' } implementation 'com.jakewharton.threetenabp:threetenabp:1.0.5' - implementation 'com.couchbase.lite:couchbase-lite-android:1.4.0' + implementation 'com.couchbase.lite:couchbase-lite-android:1.4.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20" diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index f9e6f06..9b17539 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + Unit)> = HashMap() + override fun onCreate(savedInstanceState: Bundle?) { val component: MainComponent = DaggerMainComponent.builder() @@ -30,4 +33,22 @@ class WebViewWrapperActivity: Activity() { entryViewController.loadView() } } + + /** Add a handler to be invoked on [Activity.onActivityResult] */ + fun addActivityResultHandler(identifier: Any, handler: (requestCode: Int, resultCode: Int, data: Intent?) -> Unit) { + resultHandlerMap.put(identifier, handler) + } + + /** Remove a handler not to be invoked on [Activity.onActivityResult] */ + fun removeActivityResultHandler(identifier: Any) { + resultHandlerMap.remove(identifier) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + resultHandlerMap.forEach { _, handler -> + handler.invoke(requestCode, resultCode, data) + } + } + } diff --git a/android/src/main/java/de/x4fyr/paiman/app/adapter/AndroidBase64Encoder.kt b/android/src/main/java/de/x4fyr/paiman/app/adapter/AndroidBase64Encoder.kt new file mode 100644 index 0000000..b409cfd --- /dev/null +++ b/android/src/main/java/de/x4fyr/paiman/app/adapter/AndroidBase64Encoder.kt @@ -0,0 +1,10 @@ +package de.x4fyr.paiman.app.adapter + +import android.util.Base64 + +/** + * Encoder using [android.util.Base64] + */ +class AndroidBase64Encoder : Base64Encoder() { + override fun encodeToString(byteArray: ByteArray): String = Base64.encodeToString(byteArray, Base64.DEFAULT) +} \ No newline at end of file diff --git a/android/src/main/java/de/x4fyr/paiman/app/service/AndroidPictureSelectorService.kt b/android/src/main/java/de/x4fyr/paiman/app/service/AndroidPictureSelectorService.kt index 1817b8e..4490e1e 100644 --- a/android/src/main/java/de/x4fyr/paiman/app/service/AndroidPictureSelectorService.kt +++ b/android/src/main/java/de/x4fyr/paiman/app/service/AndroidPictureSelectorService.kt @@ -1,11 +1,114 @@ package de.x4fyr.paiman.app.service +import android.app.Activity +import android.content.Context +import android.content.CursorLoader +import android.content.Intent +import android.net.Uri +import android.os.Parcelable +import android.provider.MediaStore +import android.util.Log +import de.x4fyr.paiman.app.WebViewWrapperActivity import de.x4fyr.paiman.app.services.PictureSelectorService +import java.io.File import java.io.InputStream -class AndroidPictureSelectorService: PictureSelectorService { - /** Open system dependent dialog to get a jpeg picture */ - override fun pickPicture(onReturn: (InputStream?) -> Unit) { - TODO("not implemented") +/** + * Taken an modified from https://gist.github.com/Mariovc/f06e70ebe8ca52fbbbe2 + */ +class AndroidPictureSelectorService(private var owningActivity: WebViewWrapperActivity) : PictureSelectorService { + + companion object { + private const val TAG = "ImagePicker" + private const val TEMP_IMAGE_NAME = "tempImage" + private const val PICK_IMAGE_ID = 234 //the number doesn't matter + } + + /** Action on return of the picker */ + private var onReturn: ((stream: InputStream?) -> Unit)? = null + + + init { + owningActivity.addActivityResultHandler(this) { requestCode, resultCode, intent -> + if (requestCode == PICK_IMAGE_ID) { + val url = getUrlFromResult(owningActivity, resultCode, intent) + val stream = getInputStreamFromUrl(url) + onReturn?.invoke(stream) + } + } + } + + override fun pickPicture(onReturn: (stream: InputStream?) -> Unit) { + owningActivity.startActivityForResult(getPickImageIntent(), PICK_IMAGE_ID) + this.onReturn = onReturn + } + + + private fun getPickImageIntent(): Intent? { + var chooserIntent: Intent? = null + + var intentList: MutableList = ArrayList() + + val pickIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) + val takePhotoIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + takePhotoIntent.putExtra("return-data", true) + takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(getTempFile(owningActivity))) + intentList = addIntentsToList(owningActivity, intentList, pickIntent) + intentList = addIntentsToList(owningActivity, intentList, takePhotoIntent) + + if (intentList.size > 0) { + chooserIntent = Intent.createChooser(intentList.removeAt(intentList.size - 1), "Pick Image") + chooserIntent!!.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentList.toTypedArray()) + } + + return chooserIntent } -} \ No newline at end of file + + private fun addIntentsToList(context: Context, list: MutableList, intent: Intent): MutableList { + val resInfo = context.packageManager.queryIntentActivities(intent, 0) + for (resolveInfo in resInfo) { + val packageName = resolveInfo.activityInfo.packageName + val targetedIntent = Intent(intent) + targetedIntent.`package` = packageName + list.add(targetedIntent) + Log.d(TAG, "Intent: " + intent.action + " package: " + packageName) + } + return list + } + + private fun getTempFile(context: Context): File { + val imageFile = File(context.externalCacheDir ?: context.cacheDir, TEMP_IMAGE_NAME) + imageFile.parentFile.mkdirs() + return imageFile + } + + private fun getUrlFromResult(context: Context, resultCode: Int, imageReturnedIntent: Intent?): String { + val imageFile: File = getTempFile(context) + var selectedImage: String? = null + if (resultCode == Activity.RESULT_OK) { + val intentDataString = imageReturnedIntent?.data?.toString() + if (intentDataString == null || intentDataString.contains(imageFile.toString())) { + /** CAMERA **/ + selectedImage = imageFile.path + } else { + /** ALBUM **/ + selectedImage = if (imageReturnedIntent.data.toString().contains("content:/")) { + val projection: Array = Array(1) { MediaStore.Images.Media.DATA } + val cursor = CursorLoader(context, imageReturnedIntent.data, projection, null, null, + null).loadInBackground() + val idx = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) + cursor.moveToFirst() + cursor.getString(idx) + } else { + imageReturnedIntent.data.path + } + } + } + return selectedImage ?: "" + } + + + private fun getInputStreamFromUrl(url: String): InputStream? = File(url) + .takeIf { it.exists() && it.canRead() } + ?.inputStream()?.takeIf { it.available() > 0 } +} diff --git a/android/src/main/java/de/x4fyr/paiman/app/service/AndroidWebViewService.kt b/android/src/main/java/de/x4fyr/paiman/app/service/AndroidWebViewService.kt index 2b99701..2a0b5c6 100644 --- a/android/src/main/java/de/x4fyr/paiman/app/service/AndroidWebViewService.kt +++ b/android/src/main/java/de/x4fyr/paiman/app/service/AndroidWebViewService.kt @@ -6,13 +6,14 @@ import android.util.Log import android.webkit.WebView import de.x4fyr.paiman.app.services.WebViewService import de.x4fyr.paiman.app.ui.Controller +import de.x4fyr.paiman.app.ui.Model import de.x4fyr.paiman.app.ui.produceString import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.launch import org.w3c.dom.Element /** Android implementations of [WebViewService] */ -class AndroidWebViewService(context: Context): WebViewService { +class AndroidWebViewService(private val context: Context) : WebViewService { /** WebView instance */ val webView: WebView = WebView(context) @@ -34,17 +35,36 @@ class AndroidWebViewService(context: Context): WebViewService { } } - /** Set Controller for callbacks in ui ] - * - * Suppress JavascriptInterface because the interface itself never has annotated methods, but the implementations might have. - */ + + /** Load html file into WebView */ + override fun loadHtml(html: String, controller: Controller, model: Model?) { + launch(UI) { + webView.loadUrl(viewResourcePrefix + html) + setControllerAndModel(controller, model) + } + } + + /** Set Controller for callbacks in ui */ @SuppressLint("JavascriptInterface") - override fun setCallbackController(controller: Controller) { + override fun setControllerAndModel(controller: Controller, model: Model?) { launch(UI) { webView.removeJavascriptInterface(WebViewService.javascriptControllerModuleName) + webView.removeJavascriptInterface(WebViewService.javascriptModelModuleName) webView.addJavascriptInterface(controller, WebViewService.javascriptControllerModuleName) + if (model != null) webView.addJavascriptInterface(model, WebViewService.javascriptModelModuleName) } } + /** execute javascript in current document */ + override fun executeJS(script: String) { + launch(UI) { + webView.evaluateJavascript(script) {} + } + } + + /** + * Like [WebViewService.viewResourcePrefix] but specific for android + */ + private val viewResourcePrefix: String = "file:///android_asset" + WebViewService.viewResourcePrefix.removePrefix("/assets") } \ No newline at end of file diff --git a/android/src/main/java/de/x4fyr/paiman/app/ui/controller.kt b/android/src/main/java/de/x4fyr/paiman/app/ui/controller.kt new file mode 100644 index 0000000..041202c --- /dev/null +++ b/android/src/main/java/de/x4fyr/paiman/app/ui/controller.kt @@ -0,0 +1,68 @@ +package de.x4fyr.paiman.app.ui + +import android.webkit.JavascriptInterface +import de.x4fyr.paiman.app.services.PictureSelectorService +import de.x4fyr.paiman.app.services.WebViewService +import de.x4fyr.paiman.app.ui.views.entry.EntryController +import de.x4fyr.paiman.app.ui.views.overview.OverviewController +import de.x4fyr.paiman.app.ui.views.overview.OverviewModel +import de.x4fyr.paiman.app.ui.views.paintingDetail.PaintingDetailController +import de.x4fyr.paiman.app.ui.views.paintingDetail.PaintingDetailFactory +import de.x4fyr.paiman.app.ui.views.paintingDetail.PaintingDetailModel + +class PaintingDetailControllerAndroidAdapter( + webViewService: WebViewService, + model: PaintingDetailModel, + returnController: Controller, + pictureSelectorService: PictureSelectorService +) : PaintingDetailController( webViewService, model, returnController, pictureSelectorService ) { + + /** Callback: return to [returnController] */ + @JavascriptInterface + override fun back() = super.back() + + /** Callback: add tag */ + @JavascriptInterface + override fun addTag(tag: String) = super.addTag(tag) + + /** Callback: add wip */ + @JavascriptInterface + override fun addWIP() = super.addWIP() + + /** Callback: add ref */ + @JavascriptInterface + override fun addRef() = super.addRef() + + /** Callback: finishing */ + @JavascriptInterface + override fun finishing(year: Int, month: Int) = super.finishing(year, month) +} + +/** Adapter for [OverviewController] to make functions available as javascript interface */ +class OverviewControllerAndroidAdapter(private val webViewService: WebViewService, + private val model: OverviewModel, + private val paintingDetailFactory: PaintingDetailFactory, + private val pictureSelectorService: PictureSelectorService) + : OverviewController(webViewService, model, paintingDetailFactory, pictureSelectorService) { + + /** Callback: Refresh previews */ + @JavascriptInterface + override fun refresh() = super.refresh() + + /** Callback: Open detail view of given painting by [id] */ + @JavascriptInterface + override fun openPainting(id: String) = super.openPainting(id) + + @JavascriptInterface + override fun addPainting(title: String?) = super.addPainting(title) + + @JavascriptInterface + override fun selectImage() = super.selectImage() +} + +/** Adapter for [EntryController] to make functions available as javascript interface */ +class EntryControllerAndroidAdapter(webViewService: WebViewService, mainViewController: OverviewController): EntryController(webViewService, mainViewController) { + + @JavascriptInterface + override fun openNext() = super.openNext() +} \ No newline at end of file diff --git a/android/src/main/java/de/x4fyr/paiman/app/ui/controller/EntryViewControllerAndroidAdapter.kt b/android/src/main/java/de/x4fyr/paiman/app/ui/controller/EntryViewControllerAndroidAdapter.kt deleted file mode 100644 index 0872f27..0000000 --- a/android/src/main/java/de/x4fyr/paiman/app/ui/controller/EntryViewControllerAndroidAdapter.kt +++ /dev/null @@ -1,15 +0,0 @@ -package de.x4fyr.paiman.app.ui.controller - -import android.webkit.JavascriptInterface -import de.x4fyr.paiman.app.services.WebViewService -import de.x4fyr.paiman.app.ui.views.entry.EntryController -import de.x4fyr.paiman.app.ui.views.overview.OverviewController - -/** Adapter for [EntryController] to make functions available as javascript interface */ -class EntryViewControllerAndroidAdapter(webViewService: WebViewService, mainViewController: OverviewController): EntryController(webViewService, mainViewController, entryView) { - - @JavascriptInterface - override fun openNext() { - super.openNext() - } -} \ No newline at end of file diff --git a/android/src/main/java/de/x4fyr/paiman/app/ui/controller/MainViewControllerAndroidAdapter.kt b/android/src/main/java/de/x4fyr/paiman/app/ui/controller/MainViewControllerAndroidAdapter.kt deleted file mode 100644 index 0a2c064..0000000 --- a/android/src/main/java/de/x4fyr/paiman/app/ui/controller/MainViewControllerAndroidAdapter.kt +++ /dev/null @@ -1,13 +0,0 @@ -package de.x4fyr.paiman.app.ui.controller - -import de.x4fyr.paiman.app.services.WebViewService -import de.x4fyr.paiman.app.ui.views.overview.OverviewController -import de.x4fyr.paiman.app.ui.views.overview.OverviewModel - -/** Adapter for [OverviewController] to make functions available as javascript interface */ -class MainViewControllerAndroidAdapter(webViewService: WebViewService, - model: OverviewModel, - ) - : OverviewController(webViewService, model) { - -} \ No newline at end of file diff --git a/android/src/main/java/de/x4fyr/paiman/app/ui/model.kt b/android/src/main/java/de/x4fyr/paiman/app/ui/model.kt new file mode 100644 index 0000000..8af4be5 --- /dev/null +++ b/android/src/main/java/de/x4fyr/paiman/app/ui/model.kt @@ -0,0 +1,26 @@ +package de.x4fyr.paiman.app.ui.model + +import android.webkit.JavascriptInterface +import de.x4fyr.paiman.app.adapter.Base64Encoder +import de.x4fyr.paiman.app.ui.views.overview.OverviewModel +import de.x4fyr.paiman.app.ui.views.paintingDetail.PaintingDetailModel +import de.x4fyr.paiman.lib.services.PaintingService +import de.x4fyr.paiman.lib.services.QueryService + +class OverviewModelAndroidAdapter( + paintingService: PaintingService, + queryService: QueryService, + base64Encoder: Base64Encoder +) : OverviewModel(paintingService, queryService, base64Encoder) { + @JavascriptInterface + override fun getPreviews(): String = super.getPreviews() +} + +class PaintingDetailModelAndroidAdapter( + paintingService: PaintingService, + id: String, + base64Encoder: Base64Encoder +) : PaintingDetailModel(paintingService, id, base64Encoder) { + @JavascriptInterface + override fun getHolder(): String = super.getHolder() +} \ No newline at end of file diff --git a/android/src/main/java/de/x4fyr/paiman/dagger/AndroidUIModule.java b/android/src/main/java/de/x4fyr/paiman/dagger/AndroidUIModule.java index d6e1cb6..d3f3d3f 100644 --- a/android/src/main/java/de/x4fyr/paiman/dagger/AndroidUIModule.java +++ b/android/src/main/java/de/x4fyr/paiman/dagger/AndroidUIModule.java @@ -2,8 +2,8 @@ import dagger.Module; import de.x4fyr.paiman.dagger.android.ControllerModule; -import de.x4fyr.paiman.dagger.ui.FactoryModule; -import de.x4fyr.paiman.dagger.ui.ModelModule; +import de.x4fyr.paiman.dagger.android.FactoryModule; +import de.x4fyr.paiman.dagger.android.ModelModule; @Module(includes = {ControllerModule.class, ModelModule.class, FactoryModule.class}) enum AndroidUIModule { diff --git a/android/src/main/java/de/x4fyr/paiman/dagger/android/AdapterModule.java b/android/src/main/java/de/x4fyr/paiman/dagger/android/AdapterModule.java index 65ae806..2d6d6b0 100644 --- a/android/src/main/java/de/x4fyr/paiman/dagger/android/AdapterModule.java +++ b/android/src/main/java/de/x4fyr/paiman/dagger/android/AdapterModule.java @@ -1,11 +1,11 @@ package de.x4fyr.paiman.dagger.android; +import android.content.Context; import dagger.Module; import dagger.Provides; -import de.x4fyr.paiman.app.adapter.JavaResourceAdapter; -import de.x4fyr.paiman.app.adapter.WebResourceAdapter; -import de.x4fyr.paiman.lib.adapter.AndroidGoogleDriveStorageAdapter; -import de.x4fyr.paiman.lib.adapter.GoogleDriveStorageAdapter; +import de.x4fyr.paiman.app.adapter.AndroidBase64Encoder; +import de.x4fyr.paiman.app.adapter.Base64Encoder; +import de.x4fyr.paiman.lib.adapter.AndroidStorageAdapter; import de.x4fyr.paiman.lib.adapter.StorageAdapter; import javax.inject.Singleton; @@ -17,18 +17,13 @@ public enum AdapterModule { @Provides @Singleton - static GoogleDriveStorageAdapter provideGoogleDriveStorageAdapter() { - return new AndroidGoogleDriveStorageAdapter(); + static StorageAdapter provideStorageAdapter(Context context) { + return new AndroidStorageAdapter(context); } @Provides @Singleton - static StorageAdapter provideStorageAdapter(GoogleDriveStorageAdapter googleDriveStorageAdapter) { - return googleDriveStorageAdapter; - } - - @Provides - static WebResourceAdapter resourceAdapter() { - return new JavaResourceAdapter(); + static Base64Encoder provideBase64Encoder() { + return new AndroidBase64Encoder(); } } diff --git a/android/src/main/java/de/x4fyr/paiman/dagger/android/ControllerModule.java b/android/src/main/java/de/x4fyr/paiman/dagger/android/ControllerModule.java index 6968aa3..8a4f180 100644 --- a/android/src/main/java/de/x4fyr/paiman/dagger/android/ControllerModule.java +++ b/android/src/main/java/de/x4fyr/paiman/dagger/android/ControllerModule.java @@ -2,12 +2,14 @@ import dagger.Module; import dagger.Provides; +import de.x4fyr.paiman.app.services.PictureSelectorService; import de.x4fyr.paiman.app.services.WebViewService; -import de.x4fyr.paiman.app.ui.controller.EntryViewControllerAndroidAdapter; -import de.x4fyr.paiman.app.ui.controller.MainViewControllerAndroidAdapter; +import de.x4fyr.paiman.app.ui.EntryControllerAndroidAdapter; +import de.x4fyr.paiman.app.ui.OverviewControllerAndroidAdapter; import de.x4fyr.paiman.app.ui.views.entry.EntryController; import de.x4fyr.paiman.app.ui.views.overview.OverviewController; import de.x4fyr.paiman.app.ui.views.overview.OverviewModel; +import de.x4fyr.paiman.app.ui.views.paintingDetail.PaintingDetailFactory; import javax.inject.Singleton; @@ -17,15 +19,14 @@ public enum ControllerModule { @Provides @Singleton - static OverviewController provideMainViewController(WebViewService webViewService, - OverviewModel model) { - return new MainViewControllerAndroidAdapter(webViewService, model); + static OverviewController provideOverviewController(WebViewService webViewService, OverviewModel overviewModel, PaintingDetailFactory paintingDetailFactory, PictureSelectorService pictureSelectorService) { + return new OverviewControllerAndroidAdapter(webViewService, overviewModel, paintingDetailFactory, pictureSelectorService); } @Provides @Singleton - static EntryController provideEntryViewController(WebViewService webViewService, - OverviewController overviewController) { - return new EntryViewControllerAndroidAdapter(webViewService, overviewController); + static EntryController provideEntryController(WebViewService webViewService, + OverviewController overviewController) { + return new EntryControllerAndroidAdapter(webViewService, overviewController); } } diff --git a/android/src/main/java/de/x4fyr/paiman/dagger/android/FactoryModule.kt b/android/src/main/java/de/x4fyr/paiman/dagger/android/FactoryModule.kt new file mode 100644 index 0000000..2cb11e2 --- /dev/null +++ b/android/src/main/java/de/x4fyr/paiman/dagger/android/FactoryModule.kt @@ -0,0 +1,37 @@ +package de.x4fyr.paiman.dagger.android + +import dagger.Module +import dagger.Provides +import de.x4fyr.paiman.app.adapter.Base64Encoder +import de.x4fyr.paiman.app.services.PictureSelectorService +import de.x4fyr.paiman.app.services.WebViewService +import de.x4fyr.paiman.app.ui.PaintingDetailControllerAndroidAdapter +import de.x4fyr.paiman.app.ui.model.PaintingDetailModelAndroidAdapter +import de.x4fyr.paiman.app.ui.views.paintingDetail.PaintingDetailFactory +import de.x4fyr.paiman.lib.services.PaintingService +import javax.inject.Singleton + +@Module +enum class FactoryModule { + ; + + + @Module + companion object { + + @Provides + @Singleton + @JvmStatic + internal fun providePaintingDetailFactory(paintingService: PaintingService, webViewService: WebViewService, pictureSelectorService: PictureSelectorService, base64Encoder: Base64Encoder): PaintingDetailFactory { + return PaintingDetailFactory( + paintingService, + webViewService, + pictureSelectorService, + base64Encoder, + { wVS, model, returnController, pSS -> PaintingDetailControllerAndroidAdapter(wVS, model, returnController, pSS) }, + { pS, id, b64E -> PaintingDetailModelAndroidAdapter(pS, id, b64E) } + ) + } + } + +} diff --git a/android/src/main/java/de/x4fyr/paiman/dagger/android/ModelModule.java b/android/src/main/java/de/x4fyr/paiman/dagger/android/ModelModule.java new file mode 100644 index 0000000..abe7b42 --- /dev/null +++ b/android/src/main/java/de/x4fyr/paiman/dagger/android/ModelModule.java @@ -0,0 +1,23 @@ +package de.x4fyr.paiman.dagger.android; + +import dagger.Module; +import dagger.Provides; +import de.x4fyr.paiman.app.adapter.Base64Encoder; +import de.x4fyr.paiman.app.ui.model.OverviewModelAndroidAdapter; +import de.x4fyr.paiman.app.ui.views.overview.OverviewModel; +import de.x4fyr.paiman.lib.services.PaintingService; +import de.x4fyr.paiman.lib.services.QueryService; + +import javax.inject.Singleton; + +@Module +public enum ModelModule { + ; + + @Provides + @Singleton + static OverviewModel providesOverviewModel(PaintingService paintingService, QueryService queryService, Base64Encoder base64Encoder) { + return new OverviewModelAndroidAdapter(paintingService, queryService, base64Encoder); + } + +} diff --git a/android/src/main/java/de/x4fyr/paiman/dagger/android/PlatformModule.java b/android/src/main/java/de/x4fyr/paiman/dagger/android/PlatformModule.java index ff33cb1..6d40807 100644 --- a/android/src/main/java/de/x4fyr/paiman/dagger/android/PlatformModule.java +++ b/android/src/main/java/de/x4fyr/paiman/dagger/android/PlatformModule.java @@ -3,18 +3,24 @@ import android.content.Context; import dagger.Module; import dagger.Provides; +import de.x4fyr.paiman.app.WebViewWrapperActivity; @Module public class PlatformModule { - private final Context context; + private final WebViewWrapperActivity activity; - public PlatformModule(Context context) { - this.context = context; + public PlatformModule(WebViewWrapperActivity activity) { + this.activity = activity; } @Provides Context provideContext() { - return context; + return activity; + } + + @Provides + WebViewWrapperActivity provideWebViewWrapperActivity() { + return activity; } } diff --git a/android/src/main/java/de/x4fyr/paiman/dagger/android/ServiceModule.java b/android/src/main/java/de/x4fyr/paiman/dagger/android/ServiceModule.java index aa80143..18d1fd8 100644 --- a/android/src/main/java/de/x4fyr/paiman/dagger/android/ServiceModule.java +++ b/android/src/main/java/de/x4fyr/paiman/dagger/android/ServiceModule.java @@ -3,6 +3,7 @@ import android.content.Context; import dagger.Module; import dagger.Provides; +import de.x4fyr.paiman.app.WebViewWrapperActivity; import de.x4fyr.paiman.app.service.AndroidPictureSelectorService; import de.x4fyr.paiman.app.service.AndroidWebViewService; import de.x4fyr.paiman.app.services.PictureSelectorService; @@ -27,8 +28,8 @@ static AndroidWebViewService provideWebViewServiceImpl(Context context) { } @Provides - static PictureSelectorService providePictureSelectorService() { - return new AndroidPictureSelectorService(); + static PictureSelectorService providePictureSelectorService(WebViewWrapperActivity activity) { + return new AndroidPictureSelectorService(activity); } } \ No newline at end of file diff --git a/android/src/main/java/de/x4fyr/paiman/lib/adapter/AndroidGoogleDriveStorageAdapter.kt b/android/src/main/java/de/x4fyr/paiman/lib/adapter/AndroidGoogleDriveStorageAdapter.kt deleted file mode 100644 index d7e3e87..0000000 --- a/android/src/main/java/de/x4fyr/paiman/lib/adapter/AndroidGoogleDriveStorageAdapter.kt +++ /dev/null @@ -1,79 +0,0 @@ -package de.x4fyr.paiman.lib.adapter - -import com.couchbase.lite.NetworkReachabilityManager -import com.couchbase.lite.storage.SQLiteStorageEngineFactory -import java.io.File -import java.io.InputStream -import java.util.concurrent.Future - -/** Android implementation of [GoogleDriveStorageAdapter] using PlayServices*/ -class AndroidGoogleDriveStorageAdapter: GoogleDriveStorageAdapter { - /** - * Replicators call this to get the NetworkReachabilityManager, and they register/unregister - * themselves to receive network reachability callbacks. - * - * If setNetworkReachabilityManager() was called prior to this, that instance will be used. - * Otherwise, the context will create a new default reachability manager and return that. - */ - override fun getNetworkReachabilityManager(): NetworkReachabilityManager { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - /** - * The files dir. On Android implementation, simply proxies call to underlying Context - */ - override fun getFilesDir(): File { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - /** - * Get the SQLiteStorageEngineFactory, or null if none has been set, in which case - * the default will be used. - */ - override fun getSQLiteStorageEngineFactory(): SQLiteStorageEngineFactory { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - /** - * Override the default behavior and set your own NetworkReachabilityManager subclass, - * which allows you to completely control how to respond to network reachability changes - * in your app affects the replicators that are listening for change events. - */ - override fun setNetworkReachabilityManager(networkReachabilityManager: NetworkReachabilityManager?) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - /** - * Return User-Agent value - */ - override fun getUserAgent(): String { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - /** - * Get temporary directory. The temporary directory will be used to store temporary files. - */ - override fun getTempDir(): File { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - /** Get thumbnail of image with [id] */ - override suspend fun getThumbnail(id: String): InputStream { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - /** Get image as [InputStream] from storage */ - override suspend fun getImage(id: String): InputStream { - TODO("not implemented") //TODO: not implemented - } - - /** Save image from [InputStream] to Storage */ - override suspend fun saveImage(image: InputStream): String { - TODO("not implemented") //TODO: not implemented - } - - /** Delete image from storage */ - override suspend fun deleteImage(id: String) { - TODO("not implemented") //TODO: not implemented - } -} \ No newline at end of file diff --git a/android/src/main/java/de/x4fyr/paiman/lib/adapter/AndroidStorageAdapter.kt b/android/src/main/java/de/x4fyr/paiman/lib/adapter/AndroidStorageAdapter.kt new file mode 100644 index 0000000..4b88479 --- /dev/null +++ b/android/src/main/java/de/x4fyr/paiman/lib/adapter/AndroidStorageAdapter.kt @@ -0,0 +1,121 @@ +package de.x4fyr.paiman.lib.adapter + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.Environment +import com.couchbase.lite.android.AndroidContext +import de.x4fyr.paiman.util.LRUCacheMap +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream +import java.util.* + +/** + * Android + */ +class AndroidStorageAdapter(context: Context) : StorageAdapter, AndroidContext(context) { + + private val privateDir = context.getExternalFilesDir(null) + private val publicDir = Environment.getExternalStorageDirectory().resolve(publicSubDir) + + private val imagesPath = publicDir.resolve(imagesDir) + private val thumbnailPath = privateDir.resolve(thumbnailDir) + private val couchbasePath = privateDir.resolve(couchbaseDir) + + private val imageCache = LRUCacheMap(1000) + private val thumbnailCache = LRUCacheMap(1000) + + init { + if (!publicDir.exists()) publicDir.mkdir() + if (!couchbasePath.exists()) couchbasePath.mkdir() + if (!imagesPath.exists()) imagesPath.mkdir() + if (!thumbnailPath.exists()) thumbnailPath.mkdir() + } + + /** Save image from [InputStream] to Storage */ + override suspend fun saveImage(image: InputStream): String { + val id = generateUniqueID() + writeImageToPath(image, imagesPath.resolve(id)) + return id + } + + /** Delete image from storage */ + override suspend fun deleteImage(id: String) { + imageCache.remove(id) + thumbnailCache.remove(id) + thumbnailPath.resolve(id) + .takeIf { it.exists() } + ?.also { it.delete() } + imagesPath.resolve(id) + .takeIf { it.exists() } + ?.also { it.delete() } + ?: throw StorageAdapter.StorageException.EntityDoesNotExist(id) + } + + /** Get thumbnail of image with [id] */ + override suspend fun getThumbnail(id: String): InputStream = if (thumbnailCache.containsKey(id)) { + thumbnailCache[id]!!.inputStream() + } else { + if (thumbnailPath.resolve(id).exists()) { + val byteArray = thumbnailPath.resolve(id).inputStream().readBytes() + thumbnailCache[id] = byteArray + byteArray.inputStream() + } else { + val outStream = ByteArrayOutputStream() + getImage(id) + .let { BitmapFactory.decodeStream(it) } + .let { Bitmap.createScaledBitmap(it, 100, 100, false) } + .compress(Bitmap.CompressFormat.JPEG, 98, outStream) + val byteArray = outStream.toByteArray() + thumbnailPath.resolve(id).writeBytes(byteArray) + thumbnailCache[id] = byteArray + byteArray.inputStream() + } + } + + /** Write [image] stream to [destinationPath] */ + private fun writeImageToPath(image: InputStream, destinationPath: File) { + if (destinationPath.exists()) { + destinationPath.delete() + destinationPath.createNewFile() + } + val outputStream = FileOutputStream(destinationPath) + image.copyTo(outputStream) + outputStream.close() + } + + /** Generate random image id that does not exist in [imagesDir]*/ + private fun generateUniqueID(): String { + var id = UUID.randomUUID().toString() + var path = imagesPath.resolve(id) + while (path.exists()) { + id = UUID.randomUUID().toString() + path = imagesPath.resolve(id) + } + return id + } + + /** Get image as [InputStream] from storage */ + override suspend fun getImage(id: String): InputStream = if (imageCache.containsKey(id)) { + imageCache[id]!!.inputStream() + } else { + val fileByteArray = (imagesPath.resolve(id) + .takeIf { it.exists() }?.inputStream() + ?: throw StorageAdapter.StorageException.EntityDoesNotExist(id)).readBytes() + imageCache[id] = fileByteArray + fileByteArray.inputStream() + } + + /** See [com.couchbase.lite.Context]. Overridden to modify couchbase path to central storage */ + override fun getFilesDir(): File = couchbasePath + + + companion object { + private const val publicSubDir = "Paiman" + private const val couchbaseDir = "couchbase" + private const val imagesDir = "images" + private const val thumbnailDir = "thumbnails" + } +} \ No newline at end of file diff --git a/desktop/build.gradle b/desktop/build.gradle index a38d240..0384c81 100644 --- a/desktop/build.gradle +++ b/desktop/build.gradle @@ -18,8 +18,8 @@ compileTestKotlin { }*/ dependencies { - compile 'com.couchbase.lite:couchbase-lite-java:1.4.0' - compile 'com.couchbase.lite:couchbase-lite-java-sqlcipher:1.4.0' + compile 'com.couchbase.lite:couchbase-lite-java:1.4.1' + compile 'com.couchbase.lite:couchbase-lite-java-sqlcipher:1.4.1' compile project(":libpaiman") compile project(":ui") compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" diff --git a/desktop/src/main/java/de/x4fyr/paiman/app/services/JavaFxPictureSelectorService.kt b/desktop/src/main/java/de/x4fyr/paiman/app/services/JavaFxPictureSelectorService.kt index e8ca6a5..6aa934a 100644 --- a/desktop/src/main/java/de/x4fyr/paiman/app/services/JavaFxPictureSelectorService.kt +++ b/desktop/src/main/java/de/x4fyr/paiman/app/services/JavaFxPictureSelectorService.kt @@ -1,6 +1,5 @@ package de.x4fyr.paiman.app.services -import de.x4fyr.paiman.app.transform import javafx.stage.FileChooser import javafx.stage.Stage import kotlinx.coroutines.experimental.javafx.JavaFx @@ -23,7 +22,7 @@ class JavaFxPictureSelectorService(private val stage: Stage): PictureSelectorSer FileChooser.ExtensionFilter("all", "*.*"), FileChooser.ExtensionFilter("jpeg (*.jpg,*.jpeg)", "*.jpg", "*.jpeg")) this.selectedExtensionFilter = this.extensionFilters[1] - }.showOpenDialog(stage)?.path?.transform { Paths.get(it) }?.transform { Files.newInputStream(it) }) + }.showOpenDialog(stage)?.path?.let { Paths.get(it) }?.let { Files.newInputStream(it) }) } } diff --git a/desktop/src/main/java/de/x4fyr/paiman/app/services/JavaFxWebViewService.kt b/desktop/src/main/java/de/x4fyr/paiman/app/services/JavaFxWebViewService.kt index 2335662..bcf3619 100644 --- a/desktop/src/main/java/de/x4fyr/paiman/app/services/JavaFxWebViewService.kt +++ b/desktop/src/main/java/de/x4fyr/paiman/app/services/JavaFxWebViewService.kt @@ -44,7 +44,7 @@ class JavaFxWebViewService : WebViewService { override fun loadHtml(html: String, controller: Controller, model: Model?) { launch(JavaFx) { - val url = this.javaClass.getResource("/view/$html") + val url = this.javaClass.getResource( WebViewService.viewResourcePrefix + html) webView.engine.load(url!!.toExternalForm()) //TODO: Reload only when visible setControllerAndModel(controller, model) diff --git a/desktop/src/main/java/de/x4fyr/paiman/dagger/desktop/DesktopAdapterModule.java b/desktop/src/main/java/de/x4fyr/paiman/dagger/desktop/DesktopAdapterModule.java index 9e4a8fa..e951269 100644 --- a/desktop/src/main/java/de/x4fyr/paiman/dagger/desktop/DesktopAdapterModule.java +++ b/desktop/src/main/java/de/x4fyr/paiman/dagger/desktop/DesktopAdapterModule.java @@ -2,8 +2,6 @@ import dagger.Module; import dagger.Provides; -import de.x4fyr.paiman.app.adapter.JavaResourceAdapter; -import de.x4fyr.paiman.app.adapter.WebResourceAdapter; import de.x4fyr.paiman.lib.adapter.DesktopStorageAdapter; import de.x4fyr.paiman.lib.adapter.StorageAdapter; @@ -25,8 +23,4 @@ static StorageAdapter provideStorageAdapter(DesktopStorageAdapter storageAdapter return storageAdapter; } - @Provides - static WebResourceAdapter provideResourceAdapter() { - return new JavaResourceAdapter(); - } } diff --git a/desktop/src/main/java/de/x4fyr/paiman/lib/adapter/DesktopStorageAdapter.kt b/desktop/src/main/java/de/x4fyr/paiman/lib/adapter/DesktopStorageAdapter.kt index 8d473ff..d3a7664 100644 --- a/desktop/src/main/java/de/x4fyr/paiman/lib/adapter/DesktopStorageAdapter.kt +++ b/desktop/src/main/java/de/x4fyr/paiman/lib/adapter/DesktopStorageAdapter.kt @@ -1,8 +1,7 @@ package de.x4fyr.paiman.lib.adapter import com.couchbase.lite.JavaContext -import de.x4fyr.paiman.app.LRUCacheMap -import de.x4fyr.paiman.app.transform +import de.x4fyr.paiman.util.LRUCacheMap import net.coobird.thumbnailator.Thumbnails import java.io.ByteArrayOutputStream import java.io.File @@ -11,7 +10,7 @@ import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import java.nio.file.StandardCopyOption -import java.util.UUID +import java.util.* /** Desktop implementation of [StorageAdapter] */ class DesktopStorageAdapter: StorageAdapter, JavaContext() { @@ -93,7 +92,7 @@ class DesktopStorageAdapter: StorageAdapter, JavaContext() { } else { val fileByteArray = (imagesPath.resolve(id) .takeIf { Files.exists(it) } - ?.transform { Files.newInputStream(it) } + ?.let { Files.newInputStream(it) } ?: throw StorageAdapter.StorageException.EntityDoesNotExist(id)).readBytes() imageCache.put(id, fileByteArray) fileByteArray.inputStream() diff --git a/libpaiman/build.gradle b/libpaiman/build.gradle index 57c3f99..5fd3f35 100644 --- a/libpaiman/build.gradle +++ b/libpaiman/build.gradle @@ -30,7 +30,7 @@ dependencies { compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlin_version compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '0.20' - compile 'com.couchbase.lite:couchbase-lite-java-core:1.4.0' + compile 'com.couchbase.lite:couchbase-lite-java-core:1.4.1' compile group: 'org.threeten', name:'threetenbp', version: '1.3.4' testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.assertj', name: 'assertj-core', version: '2.6.0' diff --git a/desktop/src/main/java/de/x4fyr/paiman/app/util.kt b/libpaiman/src/main/kotlin/de/x4fyr/paiman/util/LRUCacheMap.kt similarity index 60% rename from desktop/src/main/java/de/x4fyr/paiman/app/util.kt rename to libpaiman/src/main/kotlin/de/x4fyr/paiman/util/LRUCacheMap.kt index 4033d3a..8a63239 100644 --- a/desktop/src/main/java/de/x4fyr/paiman/app/util.kt +++ b/libpaiman/src/main/kotlin/de/x4fyr/paiman/util/LRUCacheMap.kt @@ -1,11 +1,4 @@ -package de.x4fyr.paiman.app - -/** [Collection.map] like function for single objects - * - * to be used with kotlin ?. null handling - */ -fun T.transform(transform: (T) -> R): R = transform(this) - +package de.x4fyr.paiman.util /** LRUCache using LinkedHashMap and overriding [removeEldestEntry] */ class LRUCacheMap(private val cacheSize: Int): LinkedHashMap(cacheSize, 0.75F, true) { diff --git a/ui/src/main/java/de/x4fyr/paiman/app/adapter/Base64Encoder.kt b/ui/src/main/java/de/x4fyr/paiman/app/adapter/Base64Encoder.kt new file mode 100644 index 0000000..c2df1dd --- /dev/null +++ b/ui/src/main/java/de/x4fyr/paiman/app/adapter/Base64Encoder.kt @@ -0,0 +1,18 @@ +package de.x4fyr.paiman.app.adapter + +import java.io.InputStream + +/** + * Base64 encoder interface + */ +abstract class Base64Encoder { + + /** + * Encode [byteArray] to [String] + */ + abstract fun encodeToString(byteArray: ByteArray): String + + internal fun jpegDataString(base64Image: String) = "data:image/jpeg;base64,$base64Image" + + internal fun jpegDataString(stream: InputStream) = jpegDataString(encodeToString(stream.readBytes())) +} \ No newline at end of file diff --git a/ui/src/main/java/de/x4fyr/paiman/app/adapter/JavaBase64Encoder.kt b/ui/src/main/java/de/x4fyr/paiman/app/adapter/JavaBase64Encoder.kt new file mode 100644 index 0000000..931d1f9 --- /dev/null +++ b/ui/src/main/java/de/x4fyr/paiman/app/adapter/JavaBase64Encoder.kt @@ -0,0 +1,13 @@ +package de.x4fyr.paiman.app.adapter + +import java.util.* + +/** + * Encoder using [java.util.Base64] + */ +class JavaBase64Encoder : Base64Encoder() { + + private val encoder = Base64.getEncoder() + override fun encodeToString(byteArray: ByteArray): String = encoder.encodeToString(byteArray) + +} diff --git a/ui/src/main/java/de/x4fyr/paiman/app/adapter/JavaResourceAdapter.kt b/ui/src/main/java/de/x4fyr/paiman/app/adapter/JavaResourceAdapter.kt deleted file mode 100644 index 2b1b0f9..0000000 --- a/ui/src/main/java/de/x4fyr/paiman/app/adapter/JavaResourceAdapter.kt +++ /dev/null @@ -1,13 +0,0 @@ -package de.x4fyr.paiman.app.adapter - -/** Pure Java implementation of [WebResourceAdapter] - * - * Uses [Class.getResourceAsStream] to load resource - */ -class JavaResourceAdapter: WebResourceAdapter { - //override fun getResourceURL(relativePath: String): String = this.javaClass.getResource(relativePath).toExternalForm() - - /** See [WebResourceAdapter.getResourceText] */ - override fun getResourceText(relativePath: String): String = this.javaClass.getResourceAsStream(relativePath) - .reader().readText() -} \ No newline at end of file diff --git a/ui/src/main/java/de/x4fyr/paiman/app/adapter/WebResourceAdapter.kt b/ui/src/main/java/de/x4fyr/paiman/app/adapter/WebResourceAdapter.kt deleted file mode 100644 index d5c0e19..0000000 --- a/ui/src/main/java/de/x4fyr/paiman/app/adapter/WebResourceAdapter.kt +++ /dev/null @@ -1,8 +0,0 @@ -package de.x4fyr.paiman.app.adapter - -/** Adapter providing access to ui/web resources */ -interface WebResourceAdapter { - - /** Get content of [relativePath] as [String] */ - fun getResourceText(relativePath: String): String -} \ No newline at end of file diff --git a/ui/src/main/java/de/x4fyr/paiman/app/services/WebViewService.kt b/ui/src/main/java/de/x4fyr/paiman/app/services/WebViewService.kt index 57a9069..57bc6da 100644 --- a/ui/src/main/java/de/x4fyr/paiman/app/services/WebViewService.kt +++ b/ui/src/main/java/de/x4fyr/paiman/app/services/WebViewService.kt @@ -27,7 +27,10 @@ interface WebViewService { companion object { /** controller interface title used in javascript/ui */ const val javascriptControllerModuleName = "controller" + /** model interface title used in javascript/ui */ const val javascriptModelModuleName = "model" + /** Prefix of resource path of ui files */ + const val viewResourcePrefix: String = "/assets/de/x4fyr/paiman/ui/" } } \ No newline at end of file diff --git a/ui/src/main/java/de/x4fyr/paiman/app/ui/const.kt b/ui/src/main/java/de/x4fyr/paiman/app/ui/const.kt new file mode 100644 index 0000000..e69de29 diff --git a/ui/src/main/java/de/x4fyr/paiman/app/ui/templates.kt b/ui/src/main/java/de/x4fyr/paiman/app/ui/templates.kt index 116e3ed..2499074 100644 --- a/ui/src/main/java/de/x4fyr/paiman/app/ui/templates.kt +++ b/ui/src/main/java/de/x4fyr/paiman/app/ui/templates.kt @@ -2,8 +2,6 @@ package de.x4fyr.paiman.app.ui import de.x4fyr.paiman.app.ui.html.onsen.* import kotlinx.html.* -import java.io.InputStream -import java.util.* /** Default head for use in Views */ fun HTML.defaultHead(block: HEAD.() -> Unit = {}) { @@ -60,6 +58,3 @@ fun HTML.defaultBodyWithoutToolbar(block: ONS_PAGE.() -> Unit) { } -internal fun jpegDataString(base64Image: String) = "data:image/jpeg;base64,$base64Image" - -internal fun jpegDataString(stream: InputStream) = jpegDataString(Base64.getEncoder().encodeToString(stream.readBytes())) diff --git a/ui/src/main/java/de/x4fyr/paiman/app/ui/views/overview/OverviewController.kt b/ui/src/main/java/de/x4fyr/paiman/app/ui/views/overview/OverviewController.kt index 7f98b32..5f22b7c 100644 --- a/ui/src/main/java/de/x4fyr/paiman/app/ui/views/overview/OverviewController.kt +++ b/ui/src/main/java/de/x4fyr/paiman/app/ui/views/overview/OverviewController.kt @@ -14,7 +14,7 @@ open class OverviewController(private val webViewService: WebViewService, private val pictureSelectorService: PictureSelectorService) : Controller { companion object { - private const val html = "html/overview.html" + private const val html = "html/overview.htm" } /** See [Controller.loadView] */ @@ -23,7 +23,7 @@ open class OverviewController(private val webViewService: WebViewService, } /** Reload view */ - suspend fun reload() { + private suspend fun reload() { //TODO: Reload only when visible webViewService.loadHtml(html, this, model) } diff --git a/ui/src/main/java/de/x4fyr/paiman/app/ui/views/overview/OverviewModel.kt b/ui/src/main/java/de/x4fyr/paiman/app/ui/views/overview/OverviewModel.kt index e0017b8..742bb69 100644 --- a/ui/src/main/java/de/x4fyr/paiman/app/ui/views/overview/OverviewModel.kt +++ b/ui/src/main/java/de/x4fyr/paiman/app/ui/views/overview/OverviewModel.kt @@ -1,8 +1,8 @@ package de.x4fyr.paiman.app.ui.views.overview import com.google.gson.Gson +import de.x4fyr.paiman.app.adapter.Base64Encoder import de.x4fyr.paiman.app.ui.Model -import de.x4fyr.paiman.app.ui.jpegDataString import de.x4fyr.paiman.lib.domain.SavedPainting import de.x4fyr.paiman.lib.services.PaintingService import de.x4fyr.paiman.lib.services.QueryService @@ -13,11 +13,12 @@ import java.util.* /** Model in overview MVC */ open class OverviewModel(private val paintingService: PaintingService, - queryService: QueryService) : Observable(), Model { + queryService: QueryService, + val base64Encoder: Base64Encoder) : Observable(), Model { //TODO: Remove observable private val gson = Gson() - val addPaintingModel = AddPaintingModel(); + val addPaintingModel = AddPaintingModel(base64Encoder = base64Encoder) private val liveQuery = queryService.allPaintingsQuery.toLiveQuery().also { @@ -37,7 +38,7 @@ open class OverviewModel(private val paintingService: PaintingService, } /** [Preview] set of all paintings*/ - var previews: Deferred> = async(CommonPool) { + private var previews: Deferred> = async(CommonPool) { paintingService.getFromQueryResult(liveQuery.also { it.waitForRows() }.rows).map { makePreviewFromPainting(it) }.toSet() @@ -60,19 +61,19 @@ open class OverviewModel(private val paintingService: PaintingService, var base64Image: String) /** Holder for "add painting" dialog */ - data class AddPaintingModel(var title: String? = null, var image: ByteArrayInputStream? = null) { + data class AddPaintingModel(var title: String? = null, var image: ByteArrayInputStream? = null, private val base64Encoder: Base64Encoder) { fun setImage(inputStream: InputStream): String { val dataString: String if (inputStream is ByteArrayInputStream) { val tmp = inputStream - dataString = jpegDataString(tmp) + dataString = base64Encoder.jpegDataString(tmp) tmp.reset() image = tmp } else { val byteArray = inputStream.readBytes() val tmp = ByteArrayInputStream(byteArray) - dataString = jpegDataString(tmp) + dataString = base64Encoder.jpegDataString(tmp) tmp.reset() image = tmp } @@ -81,7 +82,7 @@ open class OverviewModel(private val paintingService: PaintingService, } /** Save new painting and return id. null if model is not complete */ - suspend fun saveNewPainting(): String? { + internal suspend fun saveNewPainting(): String? { val title = addPaintingModel.title val image = addPaintingModel.image return if (title != null && !title.isNullOrBlank() && image != null) { @@ -93,5 +94,5 @@ open class OverviewModel(private val paintingService: PaintingService, private suspend fun makePreviewFromPainting(painting: SavedPainting): Preview = Preview(id = painting.id, title = painting.title, - base64Image = jpegDataString(paintingService.getPictureThumbnailStream(painting.mainPicture))) + base64Image = base64Encoder.jpegDataString(paintingService.getPictureThumbnailStream(painting.mainPicture))) } \ No newline at end of file diff --git a/ui/src/main/java/de/x4fyr/paiman/app/ui/views/paintingDetail/PaintingDetailFactory.kt b/ui/src/main/java/de/x4fyr/paiman/app/ui/views/paintingDetail/PaintingDetailFactory.kt index 47266b1..8aa12e1 100644 --- a/ui/src/main/java/de/x4fyr/paiman/app/ui/views/paintingDetail/PaintingDetailFactory.kt +++ b/ui/src/main/java/de/x4fyr/paiman/app/ui/views/paintingDetail/PaintingDetailFactory.kt @@ -1,5 +1,6 @@ package de.x4fyr.paiman.app.ui.views.paintingDetail +import de.x4fyr.paiman.app.adapter.Base64Encoder import de.x4fyr.paiman.app.services.PictureSelectorService import de.x4fyr.paiman.app.services.WebViewService import de.x4fyr.paiman.app.ui.Controller @@ -8,11 +9,13 @@ import de.x4fyr.paiman.lib.services.PaintingService class PaintingDetailFactory( private val paintingService: PaintingService, private val webViewService: WebViewService, - private val pictureSelectorService: PictureSelectorService -) { + private val pictureSelectorService: PictureSelectorService, + private val base64Encoder: Base64Encoder, + private val controllerFactory: (WebViewService, PaintingDetailModel, Controller, PictureSelectorService) -> PaintingDetailController, + private val modelFactory: (PaintingService, String, Base64Encoder) -> PaintingDetailModel) { fun createPaintingDetailController(id: String, returnController: Controller): PaintingDetailController { - val model = PaintingDetailModel(paintingService, id) - return PaintingDetailController(webViewService, model, returnController, pictureSelectorService) + val model = modelFactory(paintingService, id, base64Encoder) + return controllerFactory(webViewService, model, returnController, pictureSelectorService) } } \ No newline at end of file diff --git a/ui/src/main/java/de/x4fyr/paiman/app/ui/views/paintingDetail/PaintingDetailModel.kt b/ui/src/main/java/de/x4fyr/paiman/app/ui/views/paintingDetail/PaintingDetailModel.kt index e9aade5..6272d0f 100644 --- a/ui/src/main/java/de/x4fyr/paiman/app/ui/views/paintingDetail/PaintingDetailModel.kt +++ b/ui/src/main/java/de/x4fyr/paiman/app/ui/views/paintingDetail/PaintingDetailModel.kt @@ -1,8 +1,8 @@ package de.x4fyr.paiman.app.ui.views.paintingDetail import com.google.gson.Gson +import de.x4fyr.paiman.app.adapter.Base64Encoder import de.x4fyr.paiman.app.ui.Model -import de.x4fyr.paiman.app.ui.jpegDataString import de.x4fyr.paiman.lib.domain.Painting import de.x4fyr.paiman.lib.domain.SavedPainting import de.x4fyr.paiman.lib.services.PaintingService @@ -12,11 +12,11 @@ import kotlinx.coroutines.experimental.runBlocking import org.threeten.bp.LocalDate import java.io.InputStream -open class PaintingDetailModel(private val paintingService: PaintingService, val id: String) : Model { +open class PaintingDetailModel(private val paintingService: PaintingService, val id: String, val base64Encoder: Base64Encoder) : Model { private val gson = Gson() - var painting: Deferred = async { + private var painting: Deferred = async { paintingService.get(id) } @@ -25,40 +25,40 @@ open class PaintingDetailModel(private val paintingService: PaintingService, val return runBlocking { gson.toJson(createHolder(painting.await())) } } - val mainPicture: Deferred = async { - jpegDataString(paintingService.getPictureStream(painting.await().mainPicture)) + internal val mainPicture: Deferred = async { + base64Encoder.jpegDataString(paintingService.getPictureStream(painting.await().mainPicture)) } - val wips: Deferred> + private val wips: Deferred> get() = async { painting.await().wip.map { - jpegDataString(paintingService.getPictureThumbnailStream(it)) + base64Encoder.jpegDataString(paintingService.getPictureThumbnailStream(it)) } } - val refs: Deferred> + private val refs: Deferred> get() = async { painting.await().references.map { - jpegDataString(paintingService.getPictureThumbnailStream(it)) + base64Encoder.jpegDataString(paintingService.getPictureThumbnailStream(it)) } } - suspend fun addWip(stream: InputStream) { + internal suspend fun addWip(stream: InputStream) { val resultPainting = paintingService.addWipPicture(painting.await(), setOf(stream)) painting = async { resultPainting } } - suspend fun addRef(stream: InputStream) { + internal suspend fun addRef(stream: InputStream) { val resultPainting = paintingService.addReferences(painting.await(), setOf(stream)) painting = async { resultPainting } } - suspend fun addTag(tag: String) { + internal suspend fun addTag(tag: String) { val resultPainting = paintingService.addTags(painting.await(), setOf(tag)) painting = async { resultPainting } } - suspend fun finishing(date: LocalDate) { + internal suspend fun finishing(date: LocalDate) { val resultPainting: SavedPainting = paintingService.changePainting(painting.await().copy(finished = true, finishingDate = date)) painting = async { resultPainting } } @@ -72,7 +72,7 @@ open class PaintingDetailModel(private val paintingService: PaintingService, val ) private suspend fun createHolder(painting: Painting): Holder = Holder(painting.title, - jpegDataString(paintingService.getPictureStream(painting.mainPicture)), + base64Encoder.jpegDataString(paintingService.getPictureStream(painting.mainPicture)), painting.tags.toTypedArray().sortedArrayDescending(), //Reversed order because items are prepended wips.await().toTypedArray(), refs.await().toTypedArray()) diff --git a/ui/src/main/java/de/x4fyr/paiman/dagger/UIAdapterModule.java b/ui/src/main/java/de/x4fyr/paiman/dagger/UIAdapterModule.java new file mode 100644 index 0000000..7abc284 --- /dev/null +++ b/ui/src/main/java/de/x4fyr/paiman/dagger/UIAdapterModule.java @@ -0,0 +1,19 @@ +package de.x4fyr.paiman.dagger; + +import dagger.Module; +import dagger.Provides; +import de.x4fyr.paiman.app.adapter.Base64Encoder; +import de.x4fyr.paiman.app.adapter.JavaBase64Encoder; + +import javax.inject.Singleton; + +@Module +public enum UIAdapterModule { + ; + + @Provides + @Singleton + static Base64Encoder providerBase64Encoder() { + return new JavaBase64Encoder(); + } +} diff --git a/ui/src/main/java/de/x4fyr/paiman/dagger/UIModule.java b/ui/src/main/java/de/x4fyr/paiman/dagger/UIModule.java index 7e6fa41..dcc70fb 100644 --- a/ui/src/main/java/de/x4fyr/paiman/dagger/UIModule.java +++ b/ui/src/main/java/de/x4fyr/paiman/dagger/UIModule.java @@ -5,6 +5,6 @@ import de.x4fyr.paiman.dagger.ui.FactoryModule; import de.x4fyr.paiman.dagger.ui.ModelModule; -@Module(includes = {ControllerModule.class, ModelModule.class, FactoryModule.class}) +@Module(includes = {ControllerModule.class, ModelModule.class, FactoryModule.class, UIAdapterModule.class}) enum UIModule { } diff --git a/ui/src/main/java/de/x4fyr/paiman/dagger/ui/FactoryModule.java b/ui/src/main/java/de/x4fyr/paiman/dagger/ui/FactoryModule.java deleted file mode 100644 index 4b04c9e..0000000 --- a/ui/src/main/java/de/x4fyr/paiman/dagger/ui/FactoryModule.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.x4fyr.paiman.dagger.ui; - -import dagger.Module; -import dagger.Provides; -import de.x4fyr.paiman.app.services.PictureSelectorService; -import de.x4fyr.paiman.app.services.WebViewService; -import de.x4fyr.paiman.app.ui.views.paintingDetail.PaintingDetailFactory; -import de.x4fyr.paiman.lib.services.PaintingService; - -import javax.inject.Singleton; - -@Module -public enum FactoryModule { - ; - - @Provides - @Singleton - static PaintingDetailFactory providePaintingDetailFactory(PaintingService paintingService, WebViewService webViewService, PictureSelectorService pictureSelectorService) { - return new PaintingDetailFactory(paintingService, webViewService, pictureSelectorService); - } - -} diff --git a/ui/src/main/java/de/x4fyr/paiman/dagger/ui/FactoryModule.kt b/ui/src/main/java/de/x4fyr/paiman/dagger/ui/FactoryModule.kt new file mode 100644 index 0000000..2270fd0 --- /dev/null +++ b/ui/src/main/java/de/x4fyr/paiman/dagger/ui/FactoryModule.kt @@ -0,0 +1,43 @@ +package de.x4fyr.paiman.dagger.ui + +import dagger.Module +import dagger.Provides +import de.x4fyr.paiman.app.adapter.Base64Encoder +import de.x4fyr.paiman.app.services.PictureSelectorService +import de.x4fyr.paiman.app.services.WebViewService +import de.x4fyr.paiman.app.ui.views.paintingDetail.PaintingDetailController +import de.x4fyr.paiman.app.ui.views.paintingDetail.PaintingDetailFactory +import de.x4fyr.paiman.app.ui.views.paintingDetail.PaintingDetailModel +import de.x4fyr.paiman.lib.services.PaintingService +import javax.inject.Singleton + +@Module +enum class FactoryModule { + ; + + + @Module + companion object { + + @Provides + @Singleton + @JvmStatic + internal fun providePaintingDetailFactory( + paintingService: PaintingService, + webViewService: WebViewService, + pictureSelectorService: PictureSelectorService, + base64Encoder: Base64Encoder + ): PaintingDetailFactory { + return PaintingDetailFactory( + paintingService, + webViewService, + pictureSelectorService, + base64Encoder, + { wVS, model, returnController, pSS -> PaintingDetailController(wVS, model, returnController, pSS) }, + { pS, id, b64E -> PaintingDetailModel(pS, id, b64E) } + ) + + } + } + +} diff --git a/ui/src/main/java/de/x4fyr/paiman/dagger/ui/ModelModule.java b/ui/src/main/java/de/x4fyr/paiman/dagger/ui/ModelModule.java index b197e24..0c470e9 100644 --- a/ui/src/main/java/de/x4fyr/paiman/dagger/ui/ModelModule.java +++ b/ui/src/main/java/de/x4fyr/paiman/dagger/ui/ModelModule.java @@ -2,6 +2,7 @@ import dagger.Module; import dagger.Provides; +import de.x4fyr.paiman.app.adapter.Base64Encoder; import de.x4fyr.paiman.app.ui.views.overview.OverviewModel; import de.x4fyr.paiman.lib.services.PaintingService; import de.x4fyr.paiman.lib.services.QueryService; @@ -11,8 +12,8 @@ public enum ModelModule { ; @Provides - static OverviewModel provideOverviewModel(PaintingService paintingService, QueryService queryService) { - return new OverviewModel(paintingService, queryService); + static OverviewModel provideOverviewModel(PaintingService paintingService, QueryService queryService, Base64Encoder base64Encoder) { + return new OverviewModel(paintingService, queryService, base64Encoder); } } diff --git a/ui/src/main/resources/view/css/fa-svg-with-js.css b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/fa-svg-with-js.css similarity index 100% rename from ui/src/main/resources/view/css/fa-svg-with-js.css rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/fa-svg-with-js.css diff --git a/ui/src/main/resources/view/css/fontawesome-all.css b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/fontawesome-all.css similarity index 100% rename from ui/src/main/resources/view/css/fontawesome-all.css rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/fontawesome-all.css diff --git a/ui/src/main/resources/view/css/fontawesome.css b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/fontawesome.css similarity index 100% rename from ui/src/main/resources/view/css/fontawesome.css rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/fontawesome.css diff --git a/ui/src/main/resources/view/css/main.css b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/main.css similarity index 100% rename from ui/src/main/resources/view/css/main.css rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/main.css diff --git a/ui/src/main/resources/view/css/onsen-css-components.min.css b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/onsen-css-components.min.css similarity index 100% rename from ui/src/main/resources/view/css/onsen-css-components.min.css rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/onsen-css-components.min.css diff --git a/ui/src/main/resources/view/css/onsenui-core.min.css b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/onsenui-core.min.css similarity index 100% rename from ui/src/main/resources/view/css/onsenui-core.min.css rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/onsenui-core.min.css diff --git a/ui/src/main/resources/view/css/onsenui.css b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/onsenui.css similarity index 100% rename from ui/src/main/resources/view/css/onsenui.css rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/css/onsenui.css diff --git a/ui/src/main/resources/view/html/detail.html b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/html/detail.html similarity index 100% rename from ui/src/main/resources/view/html/detail.html rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/html/detail.html diff --git a/ui/src/main/resources/view/html/overview.html b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/html/overview.htm similarity index 100% rename from ui/src/main/resources/view/html/overview.html rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/html/overview.htm diff --git a/ui/src/main/resources/view/index.html b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/index.html similarity index 100% rename from ui/src/main/resources/view/index.html rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/index.html diff --git a/ui/src/main/resources/view/js/fontawesome-all.min.js b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/js/fontawesome-all.min.js similarity index 100% rename from ui/src/main/resources/view/js/fontawesome-all.min.js rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/js/fontawesome-all.min.js diff --git a/ui/src/main/resources/view/js/main.js b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/js/main.js similarity index 100% rename from ui/src/main/resources/view/js/main.js rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/js/main.js diff --git a/ui/src/main/resources/view/js/onsenui.min.js b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/js/onsenui.min.js similarity index 100% rename from ui/src/main/resources/view/js/onsenui.min.js rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/js/onsenui.min.js diff --git a/ui/src/main/resources/view/webfonts/fa-brands-400.eot b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-brands-400.eot similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-brands-400.eot rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-brands-400.eot diff --git a/ui/src/main/resources/view/webfonts/fa-brands-400.svg b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-brands-400.svg similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-brands-400.svg rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-brands-400.svg diff --git a/ui/src/main/resources/view/webfonts/fa-brands-400.ttf b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-brands-400.ttf similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-brands-400.ttf rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-brands-400.ttf diff --git a/ui/src/main/resources/view/webfonts/fa-brands-400.woff b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-brands-400.woff similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-brands-400.woff rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-brands-400.woff diff --git a/ui/src/main/resources/view/webfonts/fa-brands-400.woff2 b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-brands-400.woff2 similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-brands-400.woff2 rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-brands-400.woff2 diff --git a/ui/src/main/resources/view/webfonts/fa-regular-400.eot b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-regular-400.eot similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-regular-400.eot rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-regular-400.eot diff --git a/ui/src/main/resources/view/webfonts/fa-regular-400.svg b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-regular-400.svg similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-regular-400.svg rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-regular-400.svg diff --git a/ui/src/main/resources/view/webfonts/fa-regular-400.ttf b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-regular-400.ttf similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-regular-400.ttf rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-regular-400.ttf diff --git a/ui/src/main/resources/view/webfonts/fa-regular-400.woff b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-regular-400.woff similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-regular-400.woff rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-regular-400.woff diff --git a/ui/src/main/resources/view/webfonts/fa-regular-400.woff2 b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-regular-400.woff2 similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-regular-400.woff2 rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-regular-400.woff2 diff --git a/ui/src/main/resources/view/webfonts/fa-solid-900.eot b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-solid-900.eot similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-solid-900.eot rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-solid-900.eot diff --git a/ui/src/main/resources/view/webfonts/fa-solid-900.svg b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-solid-900.svg similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-solid-900.svg rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-solid-900.svg diff --git a/ui/src/main/resources/view/webfonts/fa-solid-900.ttf b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-solid-900.ttf similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-solid-900.ttf rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-solid-900.ttf diff --git a/ui/src/main/resources/view/webfonts/fa-solid-900.woff b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-solid-900.woff similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-solid-900.woff rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-solid-900.woff diff --git a/ui/src/main/resources/view/webfonts/fa-solid-900.woff2 b/ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-solid-900.woff2 similarity index 100% rename from ui/src/main/resources/view/webfonts/fa-solid-900.woff2 rename to ui/src/main/resources/assets/de/x4fyr/paiman/ui/webfonts/fa-solid-900.woff2