Rewrite root directory files to Kotlin

This commit is contained in:
Niko Diamadis
2022-12-26 18:37:38 +01:00
parent 0fb045ac8f
commit 91d191f01b
12 changed files with 425 additions and 517 deletions

View File

@@ -1,130 +0,0 @@
package com.github.gotify;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.style.BackgroundColorSpan;
import android.text.style.BulletSpan;
import android.text.style.QuoteSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.squareup.picasso.Picasso;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonSpansFactory;
import io.noties.markwon.MarkwonVisitor;
import io.noties.markwon.core.CorePlugin;
import io.noties.markwon.core.CoreProps;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.ext.tables.TableAwareMovementMethod;
import io.noties.markwon.ext.tables.TablePlugin;
import io.noties.markwon.image.picasso.PicassoImagesPlugin;
import io.noties.markwon.movement.MovementMethodPlugin;
import java.util.Collections;
import org.commonmark.ext.gfm.tables.TableCell;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.node.BlockQuote;
import org.commonmark.node.Code;
import org.commonmark.node.Emphasis;
import org.commonmark.node.Heading;
import org.commonmark.node.Link;
import org.commonmark.node.ListItem;
import org.commonmark.node.StrongEmphasis;
import org.commonmark.parser.Parser;
public class MarkwonFactory {
public static Markwon createForMessage(Context context, Picasso picasso) {
return Markwon.builder(context)
.usePlugin(CorePlugin.create())
.usePlugin(MovementMethodPlugin.create(TableAwareMovementMethod.create()))
.usePlugin(PicassoImagesPlugin.create(picasso))
.usePlugin(StrikethroughPlugin.create())
.usePlugin(TablePlugin.create(context))
.usePlugin(
new AbstractMarkwonPlugin() {
@Override
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
builder.linkColor(
ContextCompat.getColor(context, R.color.hyperLink))
.isLinkUnderlined(true);
}
})
.build();
}
public static Markwon createForNotification(Context context, Picasso picasso) {
final float[] headingSizes = {
2.F, 1.5F, 1.17F, 1.F, .83F, .67F,
};
final int bulletGapWidth =
(int) (8 * context.getResources().getDisplayMetrics().density + 0.5F);
return Markwon.builder(context)
.usePlugin(CorePlugin.create())
.usePlugin(PicassoImagesPlugin.create(picasso))
.usePlugin(StrikethroughPlugin.create())
.usePlugin(
new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(
@NonNull MarkwonSpansFactory.Builder builder) {
builder.setFactory(
Heading.class,
(configuration, props) ->
new Object[] {
new RelativeSizeSpan(
headingSizes[
CoreProps.HEADING_LEVEL
.require(
props)
- 1]),
new StyleSpan(Typeface.BOLD)
})
.setFactory(
Emphasis.class,
(configuration, props) ->
new StyleSpan(Typeface.ITALIC))
.setFactory(
StrongEmphasis.class,
(configuration, props) ->
new StyleSpan(Typeface.BOLD))
.setFactory(
BlockQuote.class,
(configuration, props) -> new QuoteSpan())
.setFactory(
Code.class,
(configuration, props) ->
new Object[] {
new BackgroundColorSpan(Color.LTGRAY),
new TypefaceSpan("monospace")
})
.setFactory(
ListItem.class,
(configuration, props) ->
new BulletSpan(bulletGapWidth))
.setFactory(Link.class, ((configuration, props) -> null));
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.extensions(Collections.singleton(TablesExtension.create()));
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(
TableCell.class,
(visitor, node) -> {
visitor.visitChildren(node);
visitor.builder().append(' ');
});
}
})
.build();
}
}

View File

@@ -0,0 +1,96 @@
package com.github.gotify
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.text.style.*
import androidx.core.content.ContextCompat
import com.squareup.picasso.Picasso
import io.noties.markwon.*
import io.noties.markwon.core.CorePlugin
import io.noties.markwon.core.CoreProps
import io.noties.markwon.core.MarkwonTheme
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
import io.noties.markwon.ext.tables.TableAwareMovementMethod
import io.noties.markwon.ext.tables.TablePlugin
import io.noties.markwon.image.picasso.PicassoImagesPlugin
import io.noties.markwon.movement.MovementMethodPlugin
import org.commonmark.ext.gfm.tables.TableCell
import org.commonmark.ext.gfm.tables.TablesExtension
import org.commonmark.node.*
import org.commonmark.parser.Parser
object MarkwonFactory {
fun createForMessage(context: Context, picasso: Picasso): Markwon {
return Markwon.builder(context)
.usePlugin(CorePlugin.create())
.usePlugin(MovementMethodPlugin.create(TableAwareMovementMethod.create()))
.usePlugin(PicassoImagesPlugin.create(picasso))
.usePlugin(StrikethroughPlugin.create())
.usePlugin(TablePlugin.create(context))
.usePlugin(
object : AbstractMarkwonPlugin() {
override fun configureTheme(builder: MarkwonTheme.Builder) {
builder.linkColor(ContextCompat.getColor(context, R.color.hyperLink))
.isLinkUnderlined(true)
}
})
.build()
}
fun createForNotification(context: Context, picasso: Picasso): Markwon {
val headingSizes = floatArrayOf(
2f, 1.5f, 1.17f, 1f, .83f, .67f
)
val bulletGapWidth = (8 * context.resources.displayMetrics.density + 0.5f).toInt()
return Markwon.builder(context)
.usePlugin(CorePlugin.create())
.usePlugin(PicassoImagesPlugin.create(picasso))
.usePlugin(StrikethroughPlugin.create())
.usePlugin(
object : AbstractMarkwonPlugin() {
override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) {
builder.setFactory(Heading::class.java) { _, props: RenderProps? ->
arrayOf<Any>(
RelativeSizeSpan(
headingSizes[CoreProps.HEADING_LEVEL.require(props!!) - 1]
),
StyleSpan(Typeface.BOLD)
)
}
.setFactory(Emphasis::class.java) { _, _ ->
StyleSpan(Typeface.ITALIC)
}
.setFactory(StrongEmphasis::class.java) { _, _ ->
StyleSpan(Typeface.BOLD)
}
.setFactory(BlockQuote::class.java) { _, _ -> QuoteSpan() }
.setFactory(Code::class.java) { _, _ ->
arrayOf<Any>(
BackgroundColorSpan(Color.LTGRAY),
TypefaceSpan("monospace")
)
}
.setFactory(ListItem::class.java) { _, _ ->
BulletSpan(bulletGapWidth)
}
.setFactory(Link::class.java) { _, _ -> null }
}
override fun configureParser(builder: Parser.Builder) {
builder.extensions(setOf(TablesExtension.create()))
}
override fun configureVisitor(builder: MarkwonVisitor.Builder) {
builder.on(
TableCell::class.java
) { visitor: MarkwonVisitor, node: TableCell? ->
visitor.visitChildren(node!!)
visitor.builder().append(' ')
}
}
})
.build()
}
}

View File

@@ -1,77 +0,0 @@
package com.github.gotify;
import com.github.gotify.api.Api;
import com.github.gotify.api.ApiException;
import com.github.gotify.api.Callback;
import com.github.gotify.client.api.MessageApi;
import com.github.gotify.client.model.Message;
import com.github.gotify.client.model.PagedMessages;
import com.github.gotify.log.Log;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.github.gotify.api.Callback.call;
public class MissedMessageUtil {
static final long NO_MESSAGES = 0;
private final MessageApi api;
public MissedMessageUtil(MessageApi api) {
this.api = api;
}
public void lastReceivedMessage(Callback.SuccessCallback<Long> successCallback) {
api.getMessages(1, 0L)
.enqueue(
call(
(messages) -> {
if (messages.getMessages().size() == 1) {
successCallback.onSuccess(
messages.getMessages().get(0).getId());
} else {
successCallback.onSuccess(NO_MESSAGES);
}
},
(e) -> {}));
}
public List<Message> missingMessages(long till) {
List<Message> result = new ArrayList<>();
try {
Long since = null;
while (true) {
PagedMessages pagedMessages = Api.execute(api.getMessages(10, since));
List<Message> messages = pagedMessages.getMessages();
List<Message> filtered = filter(messages, till);
result.addAll(filtered);
if (messages.size() != filtered.size()
|| messages.size() == 0
|| pagedMessages.getPaging().getNext() == null) {
break;
}
since = pagedMessages.getPaging().getSince();
}
} catch (ApiException e) {
Log.e("cannot retrieve missing messages", e);
}
Collections.reverse(result);
return result;
}
private List<Message> filter(List<Message> messages, long till) {
List<Message> result = new ArrayList<>();
for (Message message : messages) {
if (message.getId() > till) {
result.add(message);
} else {
break;
}
}
return result;
}
}

View File

@@ -0,0 +1,63 @@
package com.github.gotify
import com.github.gotify.api.Api
import com.github.gotify.api.ApiException
import com.github.gotify.api.Callback
import com.github.gotify.api.Callback.SuccessCallback
import com.github.gotify.client.api.MessageApi
import com.github.gotify.client.model.Message
import com.github.gotify.client.model.PagedMessages
import com.github.gotify.log.Log
class MissedMessageUtil(private val api: MessageApi) {
fun lastReceivedMessage(successCallback: SuccessCallback<Long?>) {
api.getMessages(1, 0L).enqueue(
Callback.call({ messages: PagedMessages? ->
if (messages!!.messages.size == 1) {
successCallback.onSuccess(messages.messages[0].id)
} else {
successCallback.onSuccess(NO_MESSAGES)
}
}
) {}
)
}
fun missingMessages(till: Long): List<Message?> {
val result = mutableListOf<Message?>()
try {
var since: Long? = null
while (true) {
val pagedMessages = Api.execute(api.getMessages(10, since))
val messages = pagedMessages!!.messages
val filtered = filter(messages, till)
result.addAll(filtered)
if (messages.size != filtered.size
|| messages.size == 0
|| pagedMessages.paging.next == null) {
break
}
since = pagedMessages.paging.since
}
} catch (e: ApiException) {
Log.e("cannot retrieve missing messages", e)
}
return result.reversed()
}
private fun filter(messages: List<Message>, till: Long): List<Message?> {
val result = mutableListOf<Message?>()
for (message in messages) {
if (message.id > till) {
result.add(message)
} else {
break
}
}
return result
}
companion object {
const val NO_MESSAGES = 0L
}
}

View File

@@ -1,107 +0,0 @@
package com.github.gotify;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.graphics.Color;
import android.os.Build;
import androidx.annotation.RequiresApi;
import com.github.gotify.log.Log;
public class NotificationSupport {
public static final class Group {
public static final String MESSAGES = "GOTIFY_GROUP_MESSAGES";
}
public static final class Channel {
public static final String FOREGROUND = "gotify_foreground";
public static final String MESSAGES_IMPORTANCE_MIN = "gotify_messages_min_importance";
public static final String MESSAGES_IMPORTANCE_LOW = "gotify_messages_low_importance";
public static final String MESSAGES_IMPORTANCE_DEFAULT =
"gotify_messages_default_importance";
public static final String MESSAGES_IMPORTANCE_HIGH = "gotify_messages_high_importance";
}
public static final class ID {
public static final int FOREGROUND = -1;
public static final int GROUPED = -2;
}
@RequiresApi(Build.VERSION_CODES.O)
public static void createChannels(NotificationManager notificationManager) {
try {
// Low importance so that persistent notification can be sorted towards bottom of
// notification shade. Also prevents vibrations caused by persistent notification
NotificationChannel foreground =
new NotificationChannel(
Channel.FOREGROUND,
"Gotify foreground notification",
NotificationManager.IMPORTANCE_LOW);
foreground.setShowBadge(false);
NotificationChannel messagesImportanceMin =
new NotificationChannel(
Channel.MESSAGES_IMPORTANCE_MIN,
"Min priority messages (<1)",
NotificationManager.IMPORTANCE_MIN);
NotificationChannel messagesImportanceLow =
new NotificationChannel(
Channel.MESSAGES_IMPORTANCE_LOW,
"Low priority messages (1-3)",
NotificationManager.IMPORTANCE_LOW);
NotificationChannel messagesImportanceDefault =
new NotificationChannel(
Channel.MESSAGES_IMPORTANCE_DEFAULT,
"Normal priority messages (4-7)",
NotificationManager.IMPORTANCE_DEFAULT);
messagesImportanceDefault.enableLights(true);
messagesImportanceDefault.setLightColor(Color.CYAN);
messagesImportanceDefault.enableVibration(true);
NotificationChannel messagesImportanceHigh =
new NotificationChannel(
Channel.MESSAGES_IMPORTANCE_HIGH,
"High priority messages (>7)",
NotificationManager.IMPORTANCE_HIGH);
messagesImportanceHigh.enableLights(true);
messagesImportanceHigh.setLightColor(Color.CYAN);
messagesImportanceHigh.enableVibration(true);
notificationManager.createNotificationChannel(foreground);
notificationManager.createNotificationChannel(messagesImportanceMin);
notificationManager.createNotificationChannel(messagesImportanceLow);
notificationManager.createNotificationChannel(messagesImportanceDefault);
notificationManager.createNotificationChannel(messagesImportanceHigh);
} catch (Exception e) {
Log.e("Could not create channel", e);
}
}
/**
* Map {@link com.github.gotify.client.model.Message#getPriority() Gotify message priorities to
* Android channels.
*
* <pre>
* Gotify Priority | Android Importance
* <= 0 | min
* 1-3 | low
* 4-7 | default
* >= 8 | high
* </pre>
*
* @param priority the Gotify priority to convert to a notification channel as a long.
* @return the identifier of the notification channel as a String.
*/
public static String convertPriorityToChannel(long priority) {
if (priority < 1) {
return Channel.MESSAGES_IMPORTANCE_MIN;
} else if (priority < 4) {
return Channel.MESSAGES_IMPORTANCE_LOW;
} else if (priority < 8) {
return Channel.MESSAGES_IMPORTANCE_DEFAULT;
} else {
return Channel.MESSAGES_IMPORTANCE_HIGH;
}
}
}

View File

@@ -0,0 +1,106 @@
package com.github.gotify
import android.app.NotificationChannel
import android.app.NotificationManager
import android.graphics.Color
import android.os.Build
import androidx.annotation.RequiresApi
import com.github.gotify.log.Log
object NotificationSupport {
@RequiresApi(Build.VERSION_CODES.O)
fun createChannels(notificationManager: NotificationManager) {
try {
// Low importance so that persistent notification can be sorted towards bottom of
// notification shade. Also prevents vibrations caused by persistent notification
val foreground = NotificationChannel(
Channel.FOREGROUND,
"Gotify foreground notification",
NotificationManager.IMPORTANCE_LOW
)
foreground.setShowBadge(false)
val messagesImportanceMin = NotificationChannel(
Channel.MESSAGES_IMPORTANCE_MIN,
"Min priority messages (<1)",
NotificationManager.IMPORTANCE_MIN
)
val messagesImportanceLow = NotificationChannel(
Channel.MESSAGES_IMPORTANCE_LOW,
"Low priority messages (1-3)",
NotificationManager.IMPORTANCE_LOW
)
val messagesImportanceDefault = NotificationChannel(
Channel.MESSAGES_IMPORTANCE_DEFAULT,
"Normal priority messages (4-7)",
NotificationManager.IMPORTANCE_DEFAULT
)
messagesImportanceDefault.enableLights(true)
messagesImportanceDefault.lightColor = Color.CYAN
messagesImportanceDefault.enableVibration(true)
val messagesImportanceHigh = NotificationChannel(
Channel.MESSAGES_IMPORTANCE_HIGH,
"High priority messages (>7)",
NotificationManager.IMPORTANCE_HIGH
)
messagesImportanceHigh.enableLights(true)
messagesImportanceHigh.lightColor = Color.CYAN
messagesImportanceHigh.enableVibration(true)
notificationManager.createNotificationChannel(foreground)
notificationManager.createNotificationChannel(messagesImportanceMin)
notificationManager.createNotificationChannel(messagesImportanceLow)
notificationManager.createNotificationChannel(messagesImportanceDefault)
notificationManager.createNotificationChannel(messagesImportanceHigh)
} catch (e: Exception) {
Log.e("Could not create channel", e)
}
}
/**
* Map {@link com.github.gotify.client.model.Message#getPriority() Gotify message priorities to
* Android channels.
*
* <pre>
* Gotify Priority | Android Importance
* <= 0 | min
* 1-3 | low
* 4-7 | default
* >= 8 | high
* </pre>
*
* @param priority the Gotify priority to convert to a notification channel as a long.
* @return the identifier of the notification channel as a String.
*/
fun convertPriorityToChannel(priority: Long): String {
return if (priority < 1) {
Channel.MESSAGES_IMPORTANCE_MIN
} else if (priority < 4) {
Channel.MESSAGES_IMPORTANCE_LOW
} else if (priority < 8) {
Channel.MESSAGES_IMPORTANCE_DEFAULT
} else {
Channel.MESSAGES_IMPORTANCE_HIGH
}
}
object Group {
const val MESSAGES = "GOTIFY_GROUP_MESSAGES"
}
object Channel {
const val FOREGROUND = "gotify_foreground"
const val MESSAGES_IMPORTANCE_MIN = "gotify_messages_min_importance"
const val MESSAGES_IMPORTANCE_LOW = "gotify_messages_low_importance"
const val MESSAGES_IMPORTANCE_DEFAULT = "gotify_messages_default_importance"
const val MESSAGES_IMPORTANCE_HIGH = "gotify_messages_high_importance"
}
object ID {
const val FOREGROUND = -1
const val GROUPED = -2
}
}

View File

@@ -1,11 +0,0 @@
package com.github.gotify;
public class SSLSettings {
public boolean validateSSL;
public String cert;
public SSLSettings(boolean validateSSL, String cert) {
this.validateSSL = validateSSL;
this.cert = cert;
}
}

View File

@@ -0,0 +1,3 @@
package com.github.gotify
class SSLSettings(val validateSSL: Boolean, val cert: String)

View File

@@ -1,82 +0,0 @@
package com.github.gotify;
import android.content.Context;
import android.content.SharedPreferences;
import com.github.gotify.client.model.User;
public class Settings {
private final SharedPreferences sharedPreferences;
public Settings(Context context) {
sharedPreferences = context.getSharedPreferences("gotify", Context.MODE_PRIVATE);
}
public void url(String url) {
sharedPreferences.edit().putString("url", url).apply();
}
public String url() {
return sharedPreferences.getString("url", null);
}
public boolean tokenExists() {
return token() != null;
}
public String token() {
return sharedPreferences.getString("token", null);
}
public void token(String token) {
sharedPreferences.edit().putString("token", token).apply();
}
public void clear() {
url(null);
token(null);
validateSSL(true);
cert(null);
}
public void user(String name, boolean admin) {
sharedPreferences.edit().putString("username", name).putBoolean("admin", admin).apply();
}
public User user() {
String username = sharedPreferences.getString("username", null);
boolean admin = sharedPreferences.getBoolean("admin", false);
if (username != null) {
return new User().name(username).admin(admin);
} else {
return new User().name("UNKNOWN").admin(false);
}
}
public String serverVersion() {
return sharedPreferences.getString("version", "UNKNOWN");
}
public void serverVersion(String version) {
sharedPreferences.edit().putString("version", version).apply();
}
private boolean validateSSL() {
return sharedPreferences.getBoolean("validateSSL", true);
}
public void validateSSL(boolean validateSSL) {
sharedPreferences.edit().putBoolean("validateSSL", validateSSL).apply();
}
private String cert() {
return sharedPreferences.getString("cert", null);
}
public void cert(String cert) {
sharedPreferences.edit().putString("cert", cert).apply();
}
public SSLSettings sslSettings() {
return new SSLSettings(validateSSL(), cert());
}
}

View File

@@ -0,0 +1,56 @@
package com.github.gotify
import android.content.Context
import android.content.SharedPreferences
import com.github.gotify.client.model.User
class Settings(context: Context) {
private val sharedPreferences: SharedPreferences
var url: String
get() = sharedPreferences.getString("url", "")!!
set(value) = sharedPreferences.edit().putString("url", value).apply()
var token: String
get() = sharedPreferences.getString("token", "")!!
set(value) = sharedPreferences.edit().putString("token", value).apply()
var user: User? = null
get() {
val username = sharedPreferences.getString("username", null)
val admin = sharedPreferences.getBoolean("admin", false)
return if (username != null) {
User().name(username).admin(admin)
} else {
User().name("UNKNOWN").admin(false)
}
}
private set
var serverVersion: String
get() = sharedPreferences.getString("version", "UNKNOWN")!!
set(value) = sharedPreferences.edit().putString("version", value).apply()
var cert: String
get() = sharedPreferences.getString("cert", "")!!
set(value) = sharedPreferences.edit().putString("cert", value).apply()
var validateSSL: Boolean
get() = sharedPreferences.getBoolean("validateSSL", true)
set(value) = sharedPreferences.edit().putBoolean("validateSSL", value).apply()
init {
sharedPreferences = context.getSharedPreferences("gotify", Context.MODE_PRIVATE)
}
fun tokenExists(): Boolean = token.isNotEmpty()
fun clear() {
url = ""
token = ""
validateSSL = true
cert = ""
}
fun setUser(name: String?, admin: Boolean) {
sharedPreferences.edit().putString("username", name).putBoolean("admin", admin).apply()
}
fun sslSettings(): SSLSettings {
return SSLSettings(validateSSL, cert)
}
}

View File

@@ -1,110 +0,0 @@
package com.github.gotify;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.format.DateUtils;
import android.view.View;
import androidx.annotation.NonNull;
import com.github.gotify.client.JSON;
import com.github.gotify.log.Log;
import com.google.android.material.snackbar.Snackbar;
import com.google.gson.Gson;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import okio.Buffer;
import org.threeten.bp.OffsetDateTime;
public class Utils {
public static final Gson JSON = new JSON().getGson();
public static void showSnackBar(Activity activity, String message) {
View rootView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
Snackbar.make(rootView, message, Snackbar.LENGTH_SHORT).show();
}
public static int longToInt(long value) {
return (int) (value % Integer.MAX_VALUE);
}
public static String dateToRelative(OffsetDateTime data) {
long time = data.toInstant().toEpochMilli();
long now = System.currentTimeMillis();
return DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS)
.toString();
}
public static String resolveAbsoluteUrl(String baseURL, String target) {
if (target == null) {
return null;
}
try {
URI targetUri = new URI(target);
if (targetUri.isAbsolute()) {
return target;
}
return new URL(new URL(baseURL), target).toString();
} catch (MalformedURLException | URISyntaxException e) {
Log.e("Could not resolve absolute url", e);
return target;
}
}
public static Target toDrawable(Resources resources, DrawableReceiver drawableReceiver) {
return new Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
drawableReceiver.loaded(new BitmapDrawable(resources, bitmap));
}
@Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {
Log.e("Bitmap failed", e);
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {}
};
}
public static String readFileFromStream(@NonNull InputStream inputStream) {
StringBuilder sb = new StringBuilder();
String currentLine;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
while ((currentLine = reader.readLine()) != null) {
sb.append(currentLine).append("\n");
}
} catch (IOException e) {
throw new IllegalArgumentException("failed to read input");
}
return sb.toString();
}
public interface DrawableReceiver {
void loaded(Drawable drawable);
}
public static InputStream stringToInputStream(String str) {
if (str == null) return null;
return new Buffer().writeUtf8(str).inputStream();
}
public static <T> T first(T[] data) {
if (data.length != 1) {
throw new IllegalArgumentException("must be one element");
}
return data[0];
}
}

View File

@@ -0,0 +1,101 @@
package com.github.gotify
import android.app.Activity
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.text.format.DateUtils
import android.view.View
import com.github.gotify.client.JSON
import com.github.gotify.log.Log
import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson
import com.squareup.picasso.Picasso.LoadedFrom
import com.squareup.picasso.Target
import okio.Buffer
import org.threeten.bp.OffsetDateTime
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.net.MalformedURLException
import java.net.URI
import java.net.URISyntaxException
import java.net.URL
object Utils {
val JSON: Gson = JSON().gson
fun showSnackBar(activity: Activity, message: String?) {
val rootView = activity.window.decorView.findViewById<View>(R.id.content)
Snackbar.make(rootView, message!!, Snackbar.LENGTH_SHORT).show()
}
fun longToInt(value: Long): Int {
return (value % Int.MAX_VALUE).toInt()
}
fun dateToRelative(data: OffsetDateTime): String {
val time = data.toInstant().toEpochMilli()
val now = System.currentTimeMillis()
return DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS)
.toString()
}
fun resolveAbsoluteUrl(baseURL: String?, target: String?): String? {
return if (target == null) {
null
} else try {
val targetUri = URI(target)
if (targetUri.isAbsolute) {
target
} else URL(URL(baseURL), target).toString()
} catch (e: MalformedURLException) {
Log.e("Could not resolve absolute url", e)
target
} catch (e: URISyntaxException) {
Log.e("Could not resolve absolute url", e)
target
}
}
fun toDrawable(resources: Resources?, drawableReceiver: DrawableReceiver): Target {
return object : Target {
override fun onBitmapLoaded(bitmap: Bitmap, from: LoadedFrom) {
drawableReceiver.loaded(BitmapDrawable(resources, bitmap))
}
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) {
Log.e("Bitmap failed", e)
}
override fun onPrepareLoad(placeHolderDrawable: Drawable) {}
}
}
fun readFileFromStream(inputStream: InputStream): String {
val sb = StringBuilder()
var currentLine: String?
try {
BufferedReader(InputStreamReader(inputStream)).use { reader ->
while (reader.readLine().also {
currentLine = it
} != null) {
sb.append(currentLine).append("\n")
}
}
} catch (e: IOException) {
throw IllegalArgumentException("failed to read input")
}
return sb.toString()
}
fun stringToInputStream(str: String?): InputStream? {
return if (str == null) null else Buffer().writeUtf8(str).inputStream()
}
fun interface DrawableReceiver {
fun loaded(drawable: Drawable?)
}
}