feat(core/message_exporter): new template design

This commit is contained in:
rhunk 2023-10-19 22:01:39 +02:00
parent 3bde12a2af
commit 5ac93fee3d
2 changed files with 327 additions and 254 deletions

View File

@ -1,166 +1,234 @@
<style>
:root {
--sigIconPrimary: #dedede;
--sigIconSecondary: #999;
--sigIconTertiary: #616161;
--sigIconNegative: #f23c57;
--sigTextPrimary: #dedede;
--sigTextPrimaryInverse: #000;
--sigTextSecondary: #999;
--sigTextTertiary: #616161;
--sigTextPlayer: #fff;
--sigTextNegative: #f23c57;
--sigColorBackgroundBorder: rgba(255, 255, 255, 0.1);
--sigBackgroundPrimary: #121212;
--sigBackgroundPrimaryInverse: #fff;
--sigBackgroundSecondary: #1e1e1e;
--sigBackgroundSecondaryHover: #2b2b2b;
--sigBackgroundFeedHover: rgba(255, 255, 255, 0.1);
--sigBackgroundMessageHover: #292929;
--sigBackgroundMessageSaved: #333232;
--sigBackgroundMessageSavedHover: #3a3a3a;
--sigMediaControlContainerBackground: rgba(255, 255, 255, 0.1);
--sigStartupFooterBackground: rgba(0, 0, 0, 0.05);
--sigButtonPrimary: #0fadff;
--sigButtonPrimaryHover: #42bfff;
--sigButtonSecondary: #2b2b2b;
--sigButtonSecondaryHover: #424242;
--sigButtonSecondaryActive: #5c5c5c;
--sigButtonTertiary: #4e565f;
--sigButtonQuaternary: #fff;
--sigButtonInactive: #1e1e1e;
--sigButtonNegative: #e1143d;
--sigButtonOnPrimary: #fff;
--sigButtonOnSecondary: #dedede;
--sigButtonOnTertiary: #fff;
--sigButtonOnQuaternary: #1e1e1e;
--sigButtonOnInactive: rgba(255, 255, 255, 0.3);
--sigButtonOnNegative: #fff;
--sigMain: #121212;
--sigSubscreen: #121212;
--sigOverlay: rgba(0, 0, 0, 0.4);
--sigOverlayHover: rgba(0, 0, 0, 0.35);
--sigSurface: #1e1e1e;
--sigSurfaceRGB: 30, 30, 30;
--sigSurfaceDown: #212121;
--sigAboveSurface: #292929;
--sigObject: rgba(255, 255, 255, 0.1);
--sigObjectDown: rgba(255, 255, 255, 0.19);
--sigConversationBoxBackground: rgba(255, 255, 255, 0.25);
--sigDivider: rgba(255, 255, 255, 0.1);
--sigDividerLight: rgba(255, 255, 255, 0.2);
--sigPlaceholder: #1e1e1e;
--sigDisabled: rgba(255, 255, 255, 0.1);
--sigCallTileHighlight: rgba(255, 255, 255, 0.8);
--sigChat: #0fadff;
--sigSnapWithoutSound: #f23c57;
--sigSnapWithSound: #a05dcd;
--sigChatSurfaceCalling: #39ca8e;
--sigChatSurfaceCallingDisabled: #105e3d;
--sigChatPending: #767676;
--sigChatPendingHover: #8f8f8f;
--sigChatIcon: #0fadff;
--sigChatIconCaret: #f8616d;
--sigChatShadowOne: 0 0 17px rgba(33, 33, 33, 0.07), 0 0 22px rgba(0, 0, 0, 0.06), 0 0 8px rgba(84, 84, 84, 0.1);
--selectedMiddleColorGradient: rgba(4, 4, 4, 0.1);
--selectedRightColorGradient: rgba(4, 4, 4, 0);
--Snap-sigIconPrimary: #dedede;
--Snap-sigIconSecondary: #999;
--Snap-sigIconTertiary: #616161;
--Snap-sigIconNegative: #f23c57;
--Snap-sigTextPrimary: #dedede;
--Snap-sigTextPrimaryInverse: #000;
--Snap-sigTextSecondary: #999;
--Snap-sigTextTertiary: #616161;
--Snap-sigTextPlayer: #fff;
--Snap-sigTextNegative: #f23c57;
--Snap-sigColorBackgroundBorder: rgba(255, 255, 255, 0.1);
--Snap-sigBackgroundPrimary: #121212;
--Snap-sigBackgroundPrimaryInverse: #fff;
--Snap-sigBackgroundSecondary: #1e1e1e;
--Snap-sigBackgroundSecondaryHover: #2b2b2b;
--Snap-sigBackgroundFeedHover: rgba(255, 255, 255, 0.1);
--Snap-sigBackgroundMessageHover: #292929;
--Snap-sigBackgroundMessageSaved: #333232;
--Snap-sigBackgroundMessageSavedHover: #3a3a3a;
--Snap-sigMediaControlContainerBackground: rgba(255, 255, 255, 0.1);
--Snap-sigStartupFooterBackground: rgba(0, 0, 0, 0.05);
--Snap-sigButtonPrimary: #0fadff;
--Snap-sigButtonPrimaryHover: #42bfff;
--Snap-sigButtonSecondary: #2b2b2b;
--Snap-sigButtonSecondaryHover: #424242;
--Snap-sigButtonSecondaryActive: #5c5c5c;
--Snap-sigButtonTertiary: #4e565f;
--Snap-sigButtonQuaternary: #fff;
--Snap-sigButtonInactive: #1e1e1e;
--Snap-sigButtonNegative: #e1143d;
--Snap-sigButtonOnPrimary: #fff;
--Snap-sigButtonOnSecondary: #dedede;
--Snap-sigButtonOnTertiary: #fff;
--Snap-sigButtonOnQuaternary: #1e1e1e;
--Snap-sigButtonOnInactive: rgba(255, 255, 255, 0.3);
--Snap-sigButtonOnNegative: #fff;
--Snap-sigMain: #121212;
--Snap-sigSubscreen: #121212;
--Snap-sigOverlay: rgba(0, 0, 0, 0.4);
--Snap-sigOverlayHover: rgba(0, 0, 0, 0.35);
--Snap-sigSurface: #1e1e1e;
--Snap-sigSurfaceRGB: 30, 30, 30;
--Snap-sigSurfaceDown: #212121;
--Snap-sigAboveSurface: #292929;
--Snap-sigObject: rgba(255, 255, 255, 0.1);
--Snap-sigObjectDown: rgba(255, 255, 255, 0.19);
--Snap-sigConversationBoxBackground: rgba(255, 255, 255, 0.25);
--Snap-sigDivider: rgba(255, 255, 255, 0.1);
--Snap-sigDividerLight: rgba(255, 255, 255, 0.2);
--Snap-sigPlaceholder: #1e1e1e;
--Snap-sigDisabled: rgba(255, 255, 255, 0.1);
--Snap-sigCallTileHighlight: rgba(255, 255, 255, 0.8);
--Snap-sigChat: #0fadff;
--Snap-sigSnapWithoutSound: #f23c57;
--Snap-sigSnapWithSound: #a05dcd;
--Snap-sigChatSurfaceCalling: #39ca8e;
--Snap-sigChatSurfaceCallingDisabled: #105e3d;
--Snap-sigChatPending: #767676;
--Snap-sigChatPendingHover: #8f8f8f;
--Snap-sigChatIcon: #0fadff;
--Snap-sigChatIconCaret: #f8616d;
--Snap-sigChatShadowOne: 0 0 17px rgba(33, 33, 33, 0.07), 0 0 22px rgba(0, 0, 0, 0.06), 0 0 8px rgba(84, 84, 84, 0.1);
--Snap-selectedMiddleColorGradient: rgba(4, 4, 4, 0.1);
--Snap-selectedRightColorGradient: rgba(4, 4, 4, 0);
--Border: 1px solid var(--Snap-sigColorBackgroundBorder);
}
body {
margin: 0;
font-family: 'Avenir Next', sans-serif;
color: var(--sigTextPrimary);
background-color: var(--sigBackgroundPrimary);
}
/* like an header */
.conversation_summary {
display: flex;
flex-direction: row;
align-items: center;
padding: 10px;
border-bottom: 1px solid var(--sigColorBackgroundBorder);
}
.conversation_message_container {
color: var(--Snap-sigTextPrimary);
background-color: var(--Snap-sigBackgroundPrimary);
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
padding: 5px;
background-color: var(--sigBackgroundSecondary);
}
.message {
display: flex;
flex-direction: row;
align-items: center;
padding: 5px;
margin-left: 5px;
border-bottom: 1px solid var(--sigColorBackgroundBorder);
justify-content: flex-start;
}
.message .header {
header {
width: 100%;
padding: 10px 0px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
header .title {
background-color: var(--Snap-sigButtonSecondary);
height: 40px;
font-weight: 600;
padding-inline-end: 16px;
border-radius: 999px;
line-height: 40px;
padding: 0 20px;
}
main {
background-color: var(--Snap-sigBackgroundSecondary);
border: var(--Border);
border-radius: 12px;
width: calc(100% - 30px);
display: flex;
flex-direction: column;
}
main>.message {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
flex-wrap: nowrap;
margin: 5px 15px;
}
main>.message .header {
width: 100%;
display: flex;
vertical-align: top;
align-self: flex-start;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
}
.message .username {
main>.message:nth-child(2n) .username {
color: #dcedc1;
}
main>.message:nth-child(2n + 1) .username {
color: #ffd3b6;
}
main>.message .username {
font-weight: bold;
}
.message .time {
main>.message .time {
color: var(--Snap-sigTextSecondary);
font-size: 12px;
color: var(--sigTextSecondary);
font-weight: 600;
}
.message .content {
margin-left: 10px;
display: flex;
flex-direction: row;
align-items: center;
main>.message:nth-child(2n) .content {
border-color: #dcedc1;
}
.chat_media {
max-width: 300px;
max-height: 500px;
main>.message:nth-child(2n + 1) .content {
border-color: #ffd3b6;
}
.overlay_media {
main>.message .content {
background-color: var(--Snap-sigBackgroundMessageSaved);
border-left: 3px solid;
border-radius: 3px;
margin-top: 4px;
padding-left: 4px;
padding: 3px 0 3px 6px;
}
main>.message .content div:has(.chat_media:not(audio):not(.overlay_media)) {
display: inline-block;
resize: horizontal;
overflow: hidden;
line-height: 0;
height: auto;
width: 300px;
}
main>.message .chat_media:not(audio):not(.overlay_media) {
width: 100%;
height: auto;
}
@-moz-document url-prefix() {
main>.message .content div {
display: inline-block;
resize: horizontal;
overflow: hidden;
line-height: 0;
height: auto;
width: 300px;
}
main>.message .chat_media:not(.overlay_media) {
width: 100%;
height: auto;
}
}
main>.message .overlay_media {
width: inherit;
height: inherit;
position: absolute;
pointer-events: none;
}
.red_snap_svg {
color: var(--sigSnapWithoutSound);
main>.message .red_snap_svg {
color: var(--Snap-sigSnapWithoutSound);
}
</style>
<body>
<div class="conversation_summary">
<header>
<div class="title"></div>
</div>
</header>
<div class="conversation_message_container"></div>
<div style="display: none;">
<main></main>
<div style="display: none;">
<svg class="red_snap_svg" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.75" y="2.75" width="10.5" height="10.5" rx="1.808" stroke="currentColor" stroke-width="1.5"></rect>
<rect x="4" y="5" width="10.5" height="10.5" rx="1.808" stroke="currentColor" stroke-width="1.5"></rect>
</svg>
</div>
<script>
const conversationData = JSON.parse(document.querySelector(".exported_content").innerHTML)
const participants = Object.values(conversationData.participants)
</div>
<script>
function base64decode(data) {
return new Uint8Array(atob(data).split('').map(c => c.charCodeAt(0)))
}
function makeConversationSummary() {
const conversationTitle = conversationData.conversationName != null ?
conversationData.conversationName : "DM with " + Object.values(participants).map(user => user.username).join(", ")
const conversationData = JSON.parse(new TextDecoder().decode(new Uint8Array(inflate(base64decode(document.querySelector(".exported_content").innerHTML)))))
const participants = Object.values(conversationData.participants)
document.querySelector(".conversation_summary .title").textContent = conversationTitle
function makeHeader() {
const conversationTitle = conversationData.conversationName != null ? conversationData.conversationName : "DM with " + Object.values(participants).map(user => user.username).join(", ")
document.querySelector("header > .title").textContent = conversationTitle
document.title = conversationTitle
}
function decodeMedia(element) {
@ -175,16 +243,16 @@
return URL.createObjectURL(new Blob([decodedData]))
}
function makeConversationMessageContainer() {
function makeMain() {
const messageTemplate = document.querySelector("#message_template")
Object.values(conversationData.messages).forEach(message => {
const messageObject = document.createElement("div")
messageObject.classList.add("message")
messageObject.appendChild(((headerElement) =>{
messageObject.appendChild(((headerElement) => {
headerElement.classList.add("header")
headerElement.appendChild(((elem) =>{
headerElement.appendChild(((elem) => {
elem.classList.add("username")
const participant = participants[message.senderId]
elem.innerHTML = (participant == null) ? "Unknown user" : participant.username
@ -192,24 +260,23 @@
})(document.createElement("div")))
headerElement.appendChild(((elem) =>{
headerElement.appendChild(((elem) => {
elem.classList.add("time")
elem.innerHTML = new Date(message.createdTimestamp).toISOString()
elem.innerHTML = new Date(message.createdTimestamp).toUTCString()
return elem
})(document.createElement("div")))
return headerElement
})(document.createElement("div")))
messageObject.appendChild(((messageContainer) =>{
messageObject.appendChild(((messageContainer) => {
messageContainer.classList.add("content")
messageContainer.innerHTML = message.serializedContent
if (!message.serializedContent) {
messageContainer.innerHTML = ""
let messageData = ""
switch(message.type) {
switch (message.type) {
case "SNAP":
messageContainer.appendChild(document.querySelector('.red_snap_svg').cloneNode(true))
messageData = "Snap"
@ -268,7 +335,7 @@
let fetched = false
new IntersectionObserver(entries => {
if(!fetched && entries[0].isIntersecting === true) {
if (!fetched && entries[0].isIntersecting === true) {
fetched = true
messageContainer.innerHTML = ""
observers.forEach(c => {
@ -285,11 +352,11 @@
return messageContainer
})(document.createElement("div")))
document.querySelector('.conversation_message_container').appendChild(messageObject)
document.querySelector('main').appendChild(messageObject)
})
}
makeConversationSummary()
makeConversationMessageContainer()
</script>
makeHeader()
makeMain()
</script>
</body>

View File

@ -2,6 +2,7 @@ package me.rhunk.snapenhance.core.messaging
import android.os.Environment
import android.util.Base64InputStream
import android.util.Base64OutputStream
import com.google.gson.JsonArray
import com.google.gson.JsonNull
import com.google.gson.JsonObject
@ -21,11 +22,7 @@ import me.rhunk.snapenhance.core.features.impl.downloader.decoder.AttachmentType
import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder
import me.rhunk.snapenhance.core.wrapper.impl.Message
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
import java.io.BufferedInputStream
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.io.*
import java.text.SimpleDateFormat
import java.util.Collections
import java.util.Date
@ -34,6 +31,7 @@ import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.zip.Deflater
import java.util.zip.DeflaterInputStream
import java.util.zip.DeflaterOutputStream
import java.util.zip.ZipFile
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
@ -172,17 +170,15 @@ class MessageExporter(
mediaFiles.forEach { (key, filePair) ->
output.write("<div class=\"media-$key\"><!-- ".toByteArray())
val deflateInputStream = DeflaterInputStream(filePair.second.inputStream(), Deflater(Deflater.BEST_COMPRESSION, true))
val base64InputStream = XposedHelpers.newInstance(
filePair.second.inputStream().use { inputStream ->
val deflateInputStream = DeflaterInputStream(inputStream, Deflater(Deflater.BEST_COMPRESSION, true))
(XposedHelpers.newInstance(
Base64InputStream::class.java,
deflateInputStream,
android.util.Base64.DEFAULT or android.util.Base64.NO_WRAP,
true
) as InputStream
base64InputStream.copyTo(output)
deflateInputStream.close()
) as InputStream).copyTo(output)
}
output.write(" --></div>\n".toByteArray())
output.flush()
updateProgress("wrote")
@ -191,7 +187,17 @@ class MessageExporter(
//write the json file
output.write("<script type=\"application/json\" class=\"exported_content\">".toByteArray())
exportJson(output)
val deflateOutputStream = DeflaterOutputStream((XposedHelpers.newInstance(
Base64OutputStream::class.java,
output,
android.util.Base64.DEFAULT or android.util.Base64.NO_WRAP,
true
) as OutputStream), Deflater(Deflater.BEST_COMPRESSION, true))
exportJson(deflateOutputStream)
deflateOutputStream.finish()
output.write("</script>\n".toByteArray())
printLog("writing template...")