Skip to content

Commit 1a00a99

Browse files
author
TeleGhost Dev
committed
Fix Chat and Sidebar props, remove interactive-widget, suppress avatar errors
1 parent b2c36c0 commit 1a00a99

File tree

9 files changed

+10986
-71
lines changed

9 files changed

+10986
-71
lines changed

android/app/src/main/java/com/teleghost/app/MainActivity.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,43 @@ class MainActivity : AppCompatActivity(), mobile.PlatformBridge {
138138
}
139139

140140
// --- PlatformBridge Implementation ---
141+
override fun openFile(path: String) {
142+
runOnUiThread {
143+
try {
144+
val file = File(path)
145+
if (!file.exists()) {
146+
android.widget.Toast.makeText(this, "File not found: $path", android.widget.Toast.LENGTH_SHORT).show()
147+
return@runOnUiThread
148+
}
149+
150+
val uri = androidx.core.content.FileProvider.getUriForFile(
151+
this,
152+
"com.teleghost.app.fileprovider",
153+
file
154+
)
155+
156+
val intent = Intent(Intent.ACTION_VIEW).apply {
157+
setDataAndType(uri, getMimeType(path))
158+
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
159+
}
160+
161+
startActivity(Intent.createChooser(intent, "Open File"))
162+
} catch (e: Exception) {
163+
android.util.Log.e(TAG, "Failed to open file", e)
164+
android.widget.Toast.makeText(this, "Open failed: ${e.message}", android.widget.Toast.LENGTH_SHORT).show()
165+
}
166+
}
167+
}
168+
169+
private fun getMimeType(url: String): String {
170+
var type: String? = null
171+
val extension = MimeTypeMap.getFileExtensionFromUrl(url)
172+
if (extension != null) {
173+
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
174+
}
175+
return type ?: "*/*"
176+
}
177+
141178
override fun pickFile() {
142179
runOnUiThread {
143180
try {

frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<head>
55
<meta charset="UTF-8" />
6-
<meta content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content" name="viewport" />
6+
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
77
<title>teleghost</title>
88
</head>
99

frontend/src/App.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,8 @@
500500
}
501501
// Убираем оптимистичное сообщение (реальное придёт через событие)
502502
messages = (messages || []).filter(m => m.ID !== tempId);
503-
await loadMessages(selectedContact.ID);
503+
// Removed redundant loadMessages to prevent UI reset loop
504+
// await loadMessages(selectedContact.ID);
504505
} catch (err) {
505506
showToast(err, 'error');
506507
// Помечаем как ошибку

frontend/src/components/Auth.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@
6060
const base64 = await GetFileBase64(p.avatar_path);
6161
if (base64) newAvatars[p.id] = base64;
6262
} catch (e) {
63-
console.error("Failed to load avatar for", p.id, e);
63+
// Suppress error if file not found, use placeholder
64+
console.warn("Avatar not found or load failed:", p.id);
6465
}
6566
}
6667
}

frontend/src/components/Chat.svelte

Lines changed: 72 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
export let onPreviewImage;
3636
export let startLoadingImage; // Fix: Add missing prop
3737
export let isLoading = false;
38+
export let previewImage; // Fix: Add missing prop
39+
export let onJumpToMessage = null; // Fix: Add missing prop
3840
3941
let textarea;
4042
let touchStartX = 0;
@@ -91,23 +93,7 @@
9193
let containerRef;
9294
9395
let isLoadingMore = false;
94-
async function handleScroll(e) {
95-
const container = e.target;
96-
const distanceToBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
97-
showScrollButton = distanceToBottom > 50;
9896
99-
// Load more when reaching top
100-
if (container.scrollTop < 100 && canLoadMore && !isLoadingMore && onLoadMore) {
101-
isLoadingMore = true;
102-
const oldHeight = container.scrollHeight;
103-
await onLoadMore();
104-
await tick();
105-
// Maintain scroll position relative to bottom
106-
const newHeight = container.scrollHeight;
107-
container.scrollTop += (newHeight - oldHeight);
108-
isLoadingMore = false;
109-
}
110-
}
11197
11298
function scrollToBottom(force = false) {
11399
// Guard against unnecessary scrolls
@@ -152,46 +138,31 @@
152138
});
153139
154140
// Handle Contact Change & Initialize Loading
155-
$: if (isLoading || (selectedContact && selectedContact.ID !== currentContactId)) {
156-
if (selectedContact) currentContactId = selectedContact.ID;
157-
// Reset state for new chat or loading start
141+
let lastMessageId = null;
142+
143+
$: if (selectedContact && selectedContact.ID !== currentContactId) {
144+
currentContactId = selectedContact.ID;
145+
// Reset state ONLY for new chat
158146
chatReady = false;
159147
initialScrollDone = false;
160148
imagesLoading = true;
161149
pendingImages = 0;
162150
loadedImages = 0;
163-
164-
// Safety timeout
165-
if (isLoading) {
166-
// If legitimate loading, we rely on isLoading becoming false
167-
} else {
168-
// Fallback if not loading but contact changed (shouldn't happen with new App logic)
169-
setTimeout(() => {
170-
if (!chatReady && !isLoading) {
171-
chatReady = true;
172-
imagesLoading = false;
173-
scrollToBottom(true);
174-
}
175-
}, 3000);
176-
}
151+
lastMessageId = null;
177152
}
178153
179154
// Handle Messages Update & Auto-scroll
180-
$: if (!isLoading && messages && currentContactId && containerRef) {
181-
// Logic when data is ready
182-
155+
$: if (messages && currentContactId && containerRef) {
183156
if (!chatReady) {
184-
// Check for images in new messages
185-
const images = messages.flatMap(m => m.Attachments || []).filter(a => a.MimeType && a.MimeType.startsWith('image/'));
157+
// Initial Load Logic
158+
const images = (messages || []).flatMap(m => m.Attachments || []).filter(a => a.MimeType && a.MimeType.startsWith('image/'));
186159
187160
if (images.length > 0) {
188-
// Check if we already counted them (to avoid reset loop)
189161
if (pendingImages === 0) {
190162
pendingImages = images.length;
191163
loadedImages = 0;
192-
// Wait for onImageLoad
193164
}
194-
// If all images loaded (rare case of cache)
165+
// If all images loaded (rare case of cache) or no pending
195166
if (loadedImages >= pendingImages) {
196167
finishLoading();
197168
}
@@ -201,45 +172,85 @@
201172
}
202173
} else {
203174
// Already ready, handle normal new message scroll
204-
const distanceToBottom = containerRef.scrollHeight - containerRef.scrollTop - containerRef.clientHeight;
205-
const wasNearBottom = distanceToBottom < 100;
206-
const isUserSender = messages.length > 0 && messages[messages.length-1].IsOutgoing;
207-
208-
if (wasNearBottom || isUserSender) {
209-
tick().then(() => {
210-
scrollToBottom(true);
211-
});
175+
const currentLastMsg = messages.length > 0 ? messages[messages.length-1] : null;
176+
const isNewMessage = currentLastMsg && currentLastMsg.ID !== lastMessageId;
177+
178+
if (isNewMessage) {
179+
lastMessageId = currentLastMsg.ID;
180+
181+
const distanceToBottom = containerRef.scrollHeight - containerRef.scrollTop - containerRef.clientHeight;
182+
const wasNearBottom = distanceToBottom < 100;
183+
const isUserSender = currentLastMsg.IsOutgoing;
184+
185+
if (wasNearBottom || isUserSender) {
186+
tick().then(() => {
187+
scrollToBottom(true);
188+
});
189+
}
212190
}
213191
}
214192
}
215193
216-
function onImageLoad() {
194+
function onImageLoad(e) {
195+
// If image is already complete (cached), handle immediately
196+
if (e && e.target && e.target.complete) {
197+
// Logic handled below
198+
}
199+
217200
if (chatReady) {
218201
// Smart scroll after image load if near bottom
219202
if (containerRef) {
220203
const dist = containerRef.scrollHeight - containerRef.scrollTop - containerRef.clientHeight;
221-
if (dist < 100) scrollToBottom(true);
204+
if (dist < 150) scrollToBottom(true);
222205
}
223206
return;
224207
}
225208
loadedImages++;
226209
if (loadedImages >= pendingImages) {
227-
chatReady = true;
228-
imagesLoading = false;
229-
scrollToBottom(true);
210+
finishLoading();
230211
}
231212
}
232213
233-
// Auto-scroll on new messages
234-
$: if (messages && messages.length > 0 && chatReady) {
235-
scrollToBottom();
236-
}
237-
238214
function finishLoading() {
239215
if (chatReady) return;
240-
chatReady = true;
241-
imagesLoading = false;
216+
217+
// 1. Force scroll to bottom WHILE INVISIBLE
242218
scrollToBottom(true);
219+
220+
// 2. Make visible after brief delay to allow layout to settle
221+
requestAnimationFrame(() => {
222+
chatReady = true;
223+
imagesLoading = false;
224+
225+
if (messages && messages.length > 0) {
226+
lastMessageId = messages[messages.length-1].ID;
227+
}
228+
229+
// 3. Force scroll again just in case opacity change affected anything
230+
setTimeout(() => scrollToBottom(true), 10);
231+
});
232+
}
233+
234+
// ...
235+
236+
async function handleScroll(e) {
237+
if (!chatReady) return; // CRITICAL: Don't load history if initial scroll isn't done
238+
239+
const container = e.target;
240+
const distanceToBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
241+
showScrollButton = distanceToBottom > 50;
242+
243+
// ... rest of logic
244+
if (container.scrollTop < 100 && canLoadMore && !isLoadingMore && onLoadMore) {
245+
// ...
246+
isLoadingMore = true;
247+
const oldHeight = container.scrollHeight;
248+
await onLoadMore();
249+
await tick();
250+
const newHeight = container.scrollHeight;
251+
container.scrollTop += (newHeight - oldHeight);
252+
isLoadingMore = false;
253+
}
243254
}
244255
245256
</script>

frontend/src/components/Sidebar.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
export let onEditFolder;
3030
export let onCreateFolder;
3131
export let onFolderContextMenu;
32+
export let onTogglePin;
33+
export let onMovePin;
3234
3335
let longPressTimer;
3436

0 commit comments

Comments
 (0)