Rewrite root directory files to Kotlin
This commit is contained in:
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
96
app/src/main/java/com/github/gotify/MarkwonFactory.kt
Normal file
96
app/src/main/java/com/github/gotify/MarkwonFactory.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
63
app/src/main/java/com/github/gotify/MissedMessageUtil.kt
Normal file
63
app/src/main/java/com/github/gotify/MissedMessageUtil.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
106
app/src/main/java/com/github/gotify/NotificationSupport.kt
Normal file
106
app/src/main/java/com/github/gotify/NotificationSupport.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
app/src/main/java/com/github/gotify/SSLSettings.kt
Normal file
3
app/src/main/java/com/github/gotify/SSLSettings.kt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package com.github.gotify
|
||||||
|
|
||||||
|
class SSLSettings(val validateSSL: Boolean, val cert: String)
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
56
app/src/main/java/com/github/gotify/Settings.kt
Normal file
56
app/src/main/java/com/github/gotify/Settings.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
101
app/src/main/java/com/github/gotify/Utils.kt
Normal file
101
app/src/main/java/com/github/gotify/Utils.kt
Normal 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?)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user