update fork #128

Manually merged
tristan merged 181 commits from mirror/monocles_chat_clean:master into master 2026-01-23 14:02:38 +01:00
5 changed files with 120 additions and 69 deletions
Showing only changes of commit e2d85da88c - Show all commits

Allow publishing video stories

Arne 2026-01-02 02:59:14 +01:00

View file

@ -7814,16 +7814,18 @@ public class XmppConnectionService extends Service {
callback.error(R.string.no_active_account, null);
return;
}
// Create a dummy conversation and message
final Conversation conversation = new Conversation(account.getDisplayName(), account, account.getJid().asBareJid(), Conversation.MODE_SINGLE);
final Message message = new Message(conversation, "", Message.ENCRYPTION_NONE);
message.setStatus(Message.STATUS_DUMMY);
if (mimeType != null && mimeType.startsWith("image/")) {
message.setType(Message.TYPE_IMAGE);
} else if (mimeType != null && mimeType.startsWith("video/")) {
message.setType(Message.TYPE_FILE);
} else {
message.setType(Message.TYPE_FILE);
}
Runnable runnable = () -> {
try {
if (mimeType != null && mimeType.startsWith("image/")) {
@ -7831,26 +7833,27 @@ public class XmppConnectionService extends Service {
} else {
getFileBackend().copyFileToPrivateStorage(message, uri, mimeType);
}
} catch (eu.siacs.conversations.persistance.FileBackend.ImageCompressionException e) {
Log.d(Config.LOGTAG, "unable to compress image for story. falling back to file transfer", e);
message.setType(Message.TYPE_FILE);
} catch (FileBackend.ImageCompressionException e) {
try {
getFileBackend().copyFileToPrivateStorage(message, uri, mimeType);
} catch (eu.siacs.conversations.persistance.FileBackend.FileCopyException ex) {
} catch (FileBackend.FileCopyException ex) {
callback.error(ex.getResId(), null);
return;
}
} catch (final eu.siacs.conversations.persistance.FileBackend.FileCopyException e) {
} catch (final FileBackend.FileCopyException e) {
callback.error(e.getResId(), null);
return;
}
mHttpConnectionManager.createNewUploadConnection(message, false, () -> {
final String url = message.getFileParams().url.toString();
if (url != null) {
callback.success(url);
} else {
callback.error(R.string.upload_failed_server_not_found, null);
try {
if (message.getFileParams() != null && message.getFileParams().url != null) {
callback.success(message.getFileParams().url.toString());
} else {
callback.error(R.string.upload_failed_server_not_found, null);
}
} finally {
getFileBackend().deleteFile(message);
}
});
};

View file

@ -162,6 +162,55 @@ public class StoriesActivity extends XmppActivity implements XmppConnectionServi
return super.onOptionsItemSelected(item);
}
private void publish(Uri uri, String mimeType) {
if (uri != null && mSelectedAccount != null) {
final EditText input = new EditText(this);
input.setHint(R.string.title_optional);
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.add_story_title)
.setView(input)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.publish, (dialog, which) -> {
final String title = input.getText().toString();
Toast.makeText(this, R.string.uploading_story, Toast.LENGTH_SHORT).show();
xmppConnectionService.uploadFileForUrl(mSelectedAccount, uri, mimeType, new UiCallback<String>() {
@Override
public void success(String url) {
xmppConnectionService.publishStory(mSelectedAccount, url, mimeType, title, new UiCallback<Void>() {
@Override
public void success(Void aVoid) {
runOnUiThread(() -> Toast.makeText(StoriesActivity.this, R.string.story_published, Toast.LENGTH_SHORT).show());
}
@Override
public void error(int errorCode, Void object) {
runOnUiThread(() -> Toast.makeText(StoriesActivity.this, errorCode, Toast.LENGTH_SHORT).show());
}
@Override
public void userInputRequired(PendingIntent pi, Void object) {
// not used
}
});
}
@Override
public void error(int errorCode, String object) {
runOnUiThread(() -> Toast.makeText(StoriesActivity.this, errorCode, Toast.LENGTH_SHORT).show());
}
@Override
public void userInputRequired(PendingIntent pi, String object) {
// not used
}
});
})
.create()
.show();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
@ -173,20 +222,24 @@ public class StoriesActivity extends XmppActivity implements XmppConnectionServi
} else if (pendingTakePhotoUri.peek() != null) {
uri = pendingTakePhotoUri.pop();
} else {
uri = null;
return; //No image was selected
}
if (uri != null && mSelectedAccount != null) {
Intent intent = new Intent(this, EditActivity.class);
intent.setData(uri);
intent.putExtra(EditActivity.KEY_CHAT_NAME, mSelectedAccount.getDisplayName());
startActivityForResult(intent, REQUEST_EDIT_STORY_IMAGE);
if (mSelectedAccount != null) {
String mimeType = getContentResolver().getType(uri);
if (mimeType != null && mimeType.startsWith("video/")) {
publish(uri, mimeType);
} else {
Intent intent = new Intent(this, EditActivity.class);
intent.setData(uri);
startActivityForResult(intent, REQUEST_EDIT_STORY_IMAGE);
}
}
} else if (requestCode == REQUEST_EDIT_STORY_IMAGE) {
Uri uri = data != null ? (Uri) data.getParcelableExtra(EditActivity.KEY_EDITED_URI) : null;
if (uri == null && data != null) {
uri = data.getData();
}
if (uri != null && mSelectedAccount != null) {
if (uri != null) {
String mimeType = data.getType();
if (mimeType == null) {
mimeType = getContentResolver().getType(uri);
@ -194,51 +247,7 @@ public class StoriesActivity extends XmppActivity implements XmppConnectionServi
if (mimeType == null) {
mimeType = "image/jpeg"; // Fallback for file URIs
}
final EditText input = new EditText(this);
input.setHint(R.string.title_optional);
Uri finalUri = uri;
String finalMimeType = mimeType;
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.add_story_title)
.setView(input)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.publish, (dialog, which) -> {
final String title = input.getText().toString();
Toast.makeText(this, R.string.uploading_story, Toast.LENGTH_SHORT).show();
xmppConnectionService.uploadFileForUrl(mSelectedAccount, finalUri, finalMimeType, new UiCallback<String>() {
@Override
public void success(String url) {
xmppConnectionService.publishStory(mSelectedAccount, url, finalMimeType, title, new UiCallback<Void>() {
@Override
public void success(Void aVoid) {
runOnUiThread(() -> Toast.makeText(StoriesActivity.this, R.string.story_published, Toast.LENGTH_SHORT).show());
}
@Override
public void error(int errorCode, Void object) {
runOnUiThread(() -> Toast.makeText(StoriesActivity.this, errorCode, Toast.LENGTH_SHORT).show());
}
@Override
public void userInputRequired(PendingIntent pi, Void object) {
// not used
}
});
}
@Override
public void error(int errorCode, String object) {
runOnUiThread(() -> Toast.makeText(StoriesActivity.this, errorCode, Toast.LENGTH_SHORT).show());
}
@Override
public void userInputRequired(PendingIntent pi, String object) {
// not used
}
});
})
.create()
.show();
publish(uri, mimeType);
}
}
} else {
@ -267,13 +276,15 @@ public class StoriesActivity extends XmppActivity implements XmppConnectionServi
}
}
private void openStoryImagePicker(Account account) {
this.mSelectedAccount = account;
if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
} else {
final Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
galleryIntent.setType("image/*");
galleryIntent.setType("image/*,video/*");
galleryIntent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
final Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
final Uri takePhotoUri = xmppConnectionService.getFileBackend().getTakePhotoUri();

View file

@ -1,6 +1,7 @@
package eu.siacs.conversations.ui;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.format.DateUtils;
import android.util.Log;
@ -11,6 +12,7 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;
import androidx.appcompat.widget.Toolbar;
import androidx.swiperefreshlayout.widget.CircularProgressDrawable;
@ -43,14 +45,17 @@ public class StoryViewActivity extends XmppActivity {
public static final String EXTRA_STORY_IDS = "story_ids";
public static final String EXTRA_ACCOUNT = "account";
public static final String EXTRA_CONTACT = "contact";
public static final String EXTRA_MIME_TYPES = "story_mime_types";
private ImageView imageView;
private VideoView videoView;
private TextView titleView;
private TextView progressView;
private ArrayList<String> urls;
private ArrayList<String> titles;
private ArrayList<String> storyIds;
private ArrayList<String> mimeTypes;
private int currentIndex = 0;
private Jid contact;
private Account mAccount;
@ -70,21 +75,25 @@ public class StoryViewActivity extends XmppActivity {
}
imageView = findViewById(R.id.story_image_view);
videoView = findViewById(R.id.story_video_view);
titleView = findViewById(R.id.story_title_view);
progressView = findViewById(R.id.story_progress_view);
urls = getIntent().getStringArrayListExtra(EXTRA_URLS);
titles = getIntent().getStringArrayListExtra(EXTRA_TITLES);
storyIds = getIntent().getStringArrayListExtra(EXTRA_STORY_IDS);
mimeTypes = getIntent().getStringArrayListExtra(EXTRA_MIME_TYPES);
imageView.setOnClickListener(v -> {
View.OnClickListener nextListener = v -> {
currentIndex++;
if (currentIndex < urls.size()) {
loadStory();
} else {
finish();
}
});
};
imageView.setOnClickListener(nextListener);
videoView.setOnClickListener(nextListener);
try {
contact = Jid.of(getIntent().getStringExtra(EXTRA_CONTACT));
@ -279,12 +288,30 @@ public class StoryViewActivity extends XmppActivity {
// Associate the downloaded file with our story message
// storyMessage.setRelativeFilePath(finalTempFile.getAbsolutePath()); // TODO: Add image support later
runOnUiThread(() -> {
circularProgressDrawable.stop();
if (!isFinishing()) {
// Now load the actual image, replacing the spinner
Glide.with(StoryViewActivity.this).load(finalTempFile).into(imageView);
String mimeType = (mimeTypes != null && currentIndex < mimeTypes.size()) ? mimeTypes.get(currentIndex) : null;
if (mimeType == null) {
mimeType = getContentResolver().getType(Uri.fromFile(finalTempFile));
}
videoView.stopPlayback(); // Stop any previous video
if (mimeType != null && mimeType.startsWith("video/")) {
imageView.setVisibility(View.GONE);
videoView.setVisibility(View.VISIBLE);
videoView.setVideoURI(Uri.fromFile(finalTempFile));
videoView.setOnPreparedListener(mp -> {
mp.setLooping(true);
videoView.start(); // Start playback here
});
} else {
videoView.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
Glide.with(StoryViewActivity.this).load(finalTempFile).into(imageView);
}
}
});
} catch (IOException e) {
Log.e(Config.LOGTAG, "Failed to download story image", e);
if (tempFile != null) {

View file

@ -133,16 +133,19 @@ public class StoryAdapter extends RecyclerView.Adapter<StoryAdapter.StoryViewHol
ArrayList<String> urls = new ArrayList<>();
ArrayList<String> titles = new ArrayList<>();
ArrayList<String> storyIds = new ArrayList<>();
ArrayList<String> mimeTypes = new ArrayList<>();
for (Story s : activity.xmppConnectionService.getStories()) {
if (s.getContact().asBareJid().equals(story.getContact().asBareJid())) {
urls.add(s.getUrl());
titles.add(s.getTitle());
storyIds.add(s.getUuid());
mimeTypes.add(s.getType());
}
}
intent.putStringArrayListExtra(StoryViewActivity.EXTRA_URLS, urls);
intent.putStringArrayListExtra(StoryViewActivity.EXTRA_TITLES, titles);
intent.putStringArrayListExtra(StoryViewActivity.EXTRA_STORY_IDS, storyIds);
intent.putStringArrayListExtra(StoryViewActivity.EXTRA_MIME_TYPES, mimeTypes);
intent.putExtra(StoryViewActivity.EXTRA_CONTACT, story.getContact().asBareJid().toString());
if (finalStoryAccount != null) {
intent.putExtra(StoryViewActivity.EXTRA_ACCOUNT, finalStoryAccount.getUuid());

View file

@ -6,6 +6,13 @@
android:fitsSystemWindows="true"
android:background="@android:color/black">
<VideoView
android:id="@+id/story_video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:visibility="gone" />
<ImageView
android:id="@+id/story_image_view"
android:layout_width="match_parent"