Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow "file:" and "[[...]]" links, display images in notes #389

Merged
merged 2 commits into from
Oct 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,10 @@ def orgJavaLocation() {
return "com.orgzly:org-java:$org_java_version"
}
}

apply plugin: 'kotlin-kapt'

dependencies {
implementation 'com.github.bumptech.glide:glide:4.8.0'
kapt 'com.github.bumptech.glide:compiler:4.8.0'
}
31 changes: 31 additions & 0 deletions app/src/main/java/com/orgzly/android/prefs/AppPreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,37 @@ public static void setLastRepeatOnTimeShift(Context context, boolean value) {
getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
}

/*
* Allow inlining images
*/
public static boolean displayInlineImages(Context context) {
return getDefaultSharedPreferences(context).getBoolean(
context.getResources().getString(R.string.pref_key_display_inline_images),
context.getResources().getBoolean(R.bool.pref_default_display_inline_images));
}

public static void displayInlineImages(Context context, boolean value) {
String key = context.getResources().getString(R.string.pref_key_display_inline_images);
getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
}

public static boolean enableImageScaling(Context context) {
return getDefaultSharedPreferences(context).getBoolean(
context.getResources().getString(R.string.pref_key_enable_image_scaling),
context.getResources().getBoolean(R.bool.pref_default_enable_image_scaling));
}

public static void enableImageScaling(Context context, boolean value) {
String key = context.getResources().getString(R.string.pref_key_enable_image_scaling);
getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
}

public static int setImageFixedWidth(Context context) {
return Integer.valueOf(getDefaultSharedPreferences(context).getString(
context.getResources().getString(R.string.pref_key_set_image_fixed_width),
context.getResources().getString(R.string.pref_default_set_image_fixed_width)));
}

/*
* Note's metadata visibility
*/
Expand Down
150 changes: 144 additions & 6 deletions app/src/main/java/com/orgzly/android/ui/ImageLoader.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
package com.orgzly.android.ui

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.os.Environment
import android.support.v4.content.FileProvider
import android.text.Spannable
import android.text.style.ImageSpan
import android.view.View
import com.orgzly.BuildConfig
import com.orgzly.android.App
import com.orgzly.android.prefs.AppPreferences
import com.orgzly.android.ui.views.TextViewWithMarkup
import com.orgzly.android.ui.views.style.FileLinkSpan
import com.orgzly.android.util.AppPermissions
import java.io.File
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import com.bumptech.glide.request.RequestOptions


object ImageLoader {
Expand All @@ -11,17 +29,137 @@ object ImageLoader {
val context = textWithMarkup.context

// Only if AppPreferences.displayImages(context) is true
// loadImages(textWithMarkup.text as Spannable)
// Setup image visualization inside the note
if ( AppPreferences.displayInlineImages(context)
// Storage permission has been granted
&& AppPermissions.isGranted(context, AppPermissions.Usage.EXTERNAL_FILES_ACCESS)) {
// Load the associated image for each FileLinkSpan
SpanUtils.forEachSpan(textWithMarkup.text as Spannable, FileLinkSpan::class.java) { span ->
loadImage(textWithMarkup, span)
}
}
}

private fun loadImages(text: Spannable) {
SpanUtils.forEachSpan(text, FileLinkSpan::class.java) { span ->
loadImage(span)
private fun loadImage(textWithMarkup: TextViewWithMarkup, span: FileLinkSpan) {
val path = span.path

if (hasSupportedExtension(path)) {
val text = textWithMarkup.text as Spannable
// Get the current context
val context = App.getAppContext()

// Get the file
val file = File(Environment.getExternalStorageDirectory(), path)

if(file.exists()) {
// Get the Uri
val contentUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file)

// Get image sizes to reduce their memory footprint by rescaling
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(Environment.getExternalStorageDirectory().toString() + "/" + path, options)

val size = fitDrawable(textWithMarkup, options.outWidth, options.outHeight)

// Setup a placeholder
val drawable = ColorDrawable(Color.TRANSPARENT)
drawable.setBounds(0, 0, size.first, size.second)

Glide.with(context)
.asBitmap()
// Use a placeholder
.apply(RequestOptions().placeholder(drawable))
// Override the bitmap size, mainly used for big images
// as it's useless to display more pixel that the pixel density allows
.apply(RequestOptions().override(size.first, size.second))
.load(contentUri)
.into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
val bd = BitmapDrawable(App.getAppContext().resources, resource)
fitDrawable(textWithMarkup, bd)
text.setSpan(ImageSpan(bd), text.getSpanStart(span), text.getSpanEnd(span), text.getSpanFlags(span))
}
})
}
}
}

private fun loadImage(span: FileLinkSpan) {
val path = span.path
fun hasSupportedExtension(path: String): Boolean {
var ret = false
var index: Int
var s = ""

// Find the last slash in the path
// to avoid case of a point in the path and a file without extension
index = path.lastIndexOf("/")

// If we found the last slash, extract the file name
if (index != -1) {
s = path.substring(index + 1)
}

// Extract the extension
index = s.lastIndexOf(".")

// If we found an extension, extract it and test it
if (index != -1) {
s = s.substring(index + 1).toLowerCase()

if (s == "jpg" || s == "jpeg" || s == "gif"
|| s == "png" || s == "bmp" || s == "webp") {
ret = true
}
}

return ret
}

fun fitDrawable(view: View, width: Int, height: Int): Pair<Int, Int> {
var newWidth = width
var newHeight = height

// Get the display metrics to be able to rescale the image if needed
val metrics = view.context.resources.displayMetrics

// Use either a fixed size or a scaled size according to user preferences
var fixedSize = -1
if (!AppPreferences.enableImageScaling(view.context)) {
fixedSize = AppPreferences.setImageFixedWidth(view.context)
}

// Before image loading view.width might not be initialized
// So we take a default maximum value that will be reduced
var maxWidth = view.width
if(maxWidth == 0)
{
maxWidth = metrics.widthPixels
}

// If we are using a fixedSize
if (fixedSize > 0) {
// Keep aspect ratio when using fixed size
val ratio = height.toFloat() / width.toFloat()
newWidth = fixedSize
newHeight = (fixedSize * ratio).toInt()
// Otherwise if we are using rescaling and the image is wider that the max width
} else if (width > maxWidth) {
//Compute image ratio
val ratio = height.toFloat() / width.toFloat()
// Ensure that the images have a minimum size
val width = Math.max(maxWidth, 256).toFloat()

newWidth = width.toInt()
newHeight = (width * ratio).toInt()
}

return Pair(newWidth, newHeight)
}

fun fitDrawable(view: View, drawable: BitmapDrawable) {
// Compute the new size of the drawable
val newSize = fitDrawable(view, drawable.bitmap.width, drawable.bitmap.height)
// Set the bounds to match the new size
drawable.setBounds(0, 0, newSize.first, newSize.second)
}
}
9 changes: 9 additions & 0 deletions app/src/main/res/values/prefs_keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,15 @@
<string name="pref_key_set_last_repeat_on_time_shift" translatable="false">pref_key_set_last_repeat_on_time_shift</string>
<bool name="pref_default_set_last_repeat_on_time_shift" translatable="false">true</bool>

<string name="pref_key_display_inline_images" translatable="false">pref_key_display_inline_images</string>
<bool name="pref_default_display_inline_images" translatable="false">true</bool>

<string name="pref_key_enable_image_scaling" translatable="false">pref_key_enable_image_scaling</string>
<bool name="pref_default_enable_image_scaling" translatable="false">true</bool>

<string name="pref_key_set_image_fixed_width" translatable="false">pref_key_set_image_fixed_width</string>
<string name="pref_default_set_image_fixed_width" translatable="false">128</string>

<!-- Separate notes with an empty line -->
<string name="pref_key_separate_notes_with_new_line" translatable="false">pref_key_separate_notes_with_new_line</string>
<string name="pref_default_separate_notes_with_new_line" translatable="false">@string/pref_value_separate_notes_with_new_line_multi_line_notes_only</string>
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -538,4 +538,11 @@

<string name="external_file_no_app_found">No application found to open this file</string>

<string name="display_inline_images">Display inline images</string>
<string name="display_inline_images_summary">Display inline images in notes</string>

<string name="enable_image_scaling">Enable image scaling</string>
<string name="enable_image_scaling_summary">Use predefined sizes or enable image scaling</string>

<string name="pref_title_set_image_fixed_width">Set inlined image fixed width (in pixels)</string>
</resources>
22 changes: 22 additions & 0 deletions app/src/main/res/xml/prefs_screen_notebooks.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/pref_title_notebooks">

<PreferenceCategory android:title="@string/notebooks">
Expand Down Expand Up @@ -148,6 +149,27 @@
android:summary="@string/set_last_repeat_on_time_shift_summary"
android:defaultValue="@bool/pref_default_set_last_repeat_on_time_shift"/>

<SwitchPreference
android:key="@string/pref_key_display_inline_images"
android:title="@string/display_inline_images"
android:summary="@string/display_inline_images_summary"
android:defaultValue="@bool/pref_default_display_inline_images"/>

<SwitchPreference
android:key="@string/pref_key_enable_image_scaling"
android:title="@string/enable_image_scaling"
android:summary="@string/enable_image_scaling_summary"
android:defaultValue="@bool/pref_default_enable_image_scaling"
android:dependency="@string/pref_key_display_inline_images"
android:disableDependentsState="true"/>

<com.orgzly.android.prefs.IntegerPreference
android:key="@string/pref_key_set_image_fixed_width"
android:title="@string/pref_title_set_image_fixed_width"
android:inputType="number"
android:defaultValue="@string/pref_default_set_image_fixed_width"
app:min="1"
android:dependency="@string/pref_key_enable_image_scaling"/>
</PreferenceCategory>

<PreferenceCategory android:title="@string/prefs_title_new_note">
Expand Down