mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-24 18:45:19 +00:00
Merge branch 'dev' into strings-squashed
This commit is contained in:
commit
eecce08c25
@ -29,7 +29,7 @@ configurations.all {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 379
|
def canonicalVersionCode = 379
|
||||||
def canonicalVersionName = "1.19.0"
|
def canonicalVersionName = "1.19.1"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
def abiPostFix = ['armeabi-v7a' : 1,
|
def abiPostFix = ['armeabi-v7a' : 1,
|
||||||
@ -246,7 +246,7 @@ dependencies {
|
|||||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||||
implementation "com.google.android.material:material:$materialVersion"
|
implementation "com.google.android.material:material:$materialVersion"
|
||||||
implementation 'com.google.android:flexbox:2.0.1'
|
implementation 'com.google.android.flexbox:flexbox:3.0.0'
|
||||||
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation "androidx.preference:preference-ktx:$preferenceVersion"
|
implementation "androidx.preference:preference-ktx:$preferenceVersion"
|
||||||
@ -273,11 +273,11 @@ dependencies {
|
|||||||
|
|
||||||
if (project.hasProperty('huawei')) huaweiImplementation 'com.huawei.hms:push:6.7.0.300'
|
if (project.hasProperty('huawei')) huaweiImplementation 'com.huawei.hms:push:6.7.0.300'
|
||||||
|
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
|
implementation 'androidx.media3:media3-exoplayer:1.4.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
|
implementation 'androidx.media3:media3-ui:1.4.0'
|
||||||
implementation 'org.conscrypt:conscrypt-android:2.5.2'
|
implementation 'org.conscrypt:conscrypt-android:2.5.2'
|
||||||
implementation 'org.signal:aesgcmprovider:0.0.3'
|
implementation 'org.signal:aesgcmprovider:0.0.3'
|
||||||
implementation 'org.webrtc:google-webrtc:1.0.32006'
|
implementation 'io.github.webrtc-sdk:android:125.6422.04'
|
||||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
implementation "me.leolin:ShortcutBadger:1.1.16"
|
||||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||||
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
|
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||||
@ -289,17 +289,13 @@ dependencies {
|
|||||||
implementation 'com.pnikosis:materialish-progress:1.5'
|
implementation 'com.pnikosis:materialish-progress:1.5'
|
||||||
implementation 'org.greenrobot:eventbus:3.0.0'
|
implementation 'org.greenrobot:eventbus:3.0.0'
|
||||||
implementation 'pl.tajchert:waitingdots:0.1.0'
|
implementation 'pl.tajchert:waitingdots:0.1.0'
|
||||||
implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
|
implementation 'com.vanniktech:android-image-cropper:4.5.0'
|
||||||
implementation 'com.melnykov:floatingactionbutton:1.3.0'
|
implementation 'com.melnykov:floatingactionbutton:1.3.0'
|
||||||
implementation 'com.google.zxing:android-integration:3.1.0'
|
implementation 'com.google.zxing:android-integration:3.1.0'
|
||||||
implementation 'mobi.upod:time-duration-picker:1.1.3'
|
|
||||||
implementation 'com.google.zxing:core:3.2.1'
|
implementation 'com.google.zxing:core:3.2.1'
|
||||||
implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') {
|
implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') {
|
||||||
exclude group: 'com.android.support', module: 'support-annotations'
|
exclude group: 'com.android.support', module: 'support-annotations'
|
||||||
}
|
}
|
||||||
implementation ('cn.carbswang.android:NumberPickerView:1.0.9') {
|
|
||||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
|
||||||
}
|
|
||||||
implementation ('com.tomergoldst.android:tooltips:1.0.6') {
|
implementation ('com.tomergoldst.android:tooltips:1.0.6') {
|
||||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||||
}
|
}
|
||||||
@ -308,7 +304,7 @@ dependencies {
|
|||||||
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
|
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
|
||||||
}
|
}
|
||||||
implementation 'com.annimon:stream:1.1.8'
|
implementation 'com.annimon:stream:1.1.8'
|
||||||
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
|
implementation project(':stickyheader')
|
||||||
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
|
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
|
||||||
implementation 'androidx.sqlite:sqlite-ktx:2.3.1'
|
implementation 'androidx.sqlite:sqlite-ktx:2.3.1'
|
||||||
implementation 'net.zetetic:sqlcipher-android:4.5.4@aar'
|
implementation 'net.zetetic:sqlcipher-android:4.5.4@aar'
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk tools:overrideLibrary="com.astuetz.pagerslidingtabstrip,pl.tajchert.waitingdots,com.h6ah4i.android.multiselectlistpreferencecompat,android.support.v13,com.davemorrissey.labs.subscaleview,com.tomergoldst.tooltips,com.klinker.android.send_message,com.takisoft.colorpicker,android.support.v14.preference" />
|
||||||
tools:overrideLibrary="com.amulyakhare.textdrawable,com.astuetz.pagerslidingtabstrip,pl.tajchert.waitingdots,com.h6ah4i.android.multiselectlistpreferencecompat,android.support.v13,com.davemorrissey.labs.subscaleview,com.tomergoldst.tooltips,com.klinker.android.send_message,com.takisoft.colorpicker,android.support.v14.preference" />
|
|
||||||
|
|
||||||
<permission
|
<permission
|
||||||
android:name="network.loki.messenger.ACCESS_SESSION_SECRETS"
|
android:name="network.loki.messenger.ACCESS_SESSION_SECRETS"
|
||||||
|
@ -23,7 +23,6 @@ import android.app.Application;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
|
|
||||||
@ -91,8 +90,6 @@ import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
|||||||
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
||||||
import org.webrtc.PeerConnectionFactory;
|
import org.webrtc.PeerConnectionFactory;
|
||||||
import org.webrtc.PeerConnectionFactory.InitializationOptions;
|
import org.webrtc.PeerConnectionFactory.InitializationOptions;
|
||||||
import org.webrtc.voiceengine.WebRtcAudioManager;
|
|
||||||
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -100,9 +97,7 @@ import java.io.InputStream;
|
|||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
@ -394,33 +389,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
|
|
||||||
private void initializeWebRtc() {
|
private void initializeWebRtc() {
|
||||||
try {
|
try {
|
||||||
Set<String> HARDWARE_AEC_BLACKLIST = new HashSet<String>() {{
|
|
||||||
add("Pixel");
|
|
||||||
add("Pixel XL");
|
|
||||||
add("Moto G5");
|
|
||||||
add("Moto G (5S) Plus");
|
|
||||||
add("Moto G4");
|
|
||||||
add("TA-1053");
|
|
||||||
add("Mi A1");
|
|
||||||
add("E5823"); // Sony z5 compact
|
|
||||||
add("Redmi Note 5");
|
|
||||||
add("FP2"); // Fairphone FP2
|
|
||||||
add("MI 5");
|
|
||||||
}};
|
|
||||||
|
|
||||||
Set<String> OPEN_SL_ES_WHITELIST = new HashSet<String>() {{
|
|
||||||
add("Pixel");
|
|
||||||
add("Pixel XL");
|
|
||||||
}};
|
|
||||||
|
|
||||||
if (HARDWARE_AEC_BLACKLIST.contains(Build.MODEL)) {
|
|
||||||
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!OPEN_SL_ES_WHITELIST.contains(Build.MODEL)) {
|
|
||||||
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
PeerConnectionFactory.initialize(InitializationOptions.builder(this).createInitializationOptions());
|
PeerConnectionFactory.initialize(InitializationOptions.builder(this).createInitializationOptions());
|
||||||
} catch (UnsatisfiedLinkError e) {
|
} catch (UnsatisfiedLinkError e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
|
@ -6,8 +6,6 @@ import android.hardware.SensorEvent;
|
|||||||
import android.hardware.SensorEventListener;
|
import android.hardware.SensorEventListener;
|
||||||
import android.hardware.SensorManager;
|
import android.hardware.SensorManager;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
@ -15,24 +13,17 @@ import android.os.PowerManager;
|
|||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import androidx.annotation.OptIn;
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
import androidx.media3.common.AudioAttributes;
|
||||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
import androidx.media3.common.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import androidx.media3.common.MediaItem;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import androidx.media3.common.PlaybackException;
|
||||||
import com.google.android.exoplayer2.LoadControl;
|
import androidx.media3.common.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import androidx.media3.common.Player;
|
||||||
import com.google.android.exoplayer2.Player;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
|
||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import network.loki.messenger.BuildConfig;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.session.libsession.utilities.ServiceUtil;
|
import org.session.libsession.utilities.ServiceUtil;
|
||||||
import org.session.libsession.utilities.Util;
|
import org.session.libsession.utilities.Util;
|
||||||
@ -56,7 +47,7 @@ public class AudioSlidePlayer implements SensorEventListener {
|
|||||||
private final @Nullable WakeLock wakeLock;
|
private final @Nullable WakeLock wakeLock;
|
||||||
|
|
||||||
private @NonNull WeakReference<Listener> listener;
|
private @NonNull WeakReference<Listener> listener;
|
||||||
private @Nullable SimpleExoPlayer mediaPlayer;
|
private @Nullable ExoPlayer mediaPlayer;
|
||||||
private @Nullable AttachmentServer audioAttachmentServer;
|
private @Nullable AttachmentServer audioAttachmentServer;
|
||||||
private long startTime;
|
private long startTime;
|
||||||
|
|
||||||
@ -89,40 +80,38 @@ public class AudioSlidePlayer implements SensorEventListener {
|
|||||||
this.sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
this.sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||||
this.proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
|
this.proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
this.wakeLock = ServiceUtil.getPowerManager(context).newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
|
||||||
this.wakeLock = ServiceUtil.getPowerManager(context).newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
|
|
||||||
} else {
|
|
||||||
this.wakeLock = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void play(final double progress) throws IOException {
|
public void play(final double progress) throws IOException {
|
||||||
play(progress, false);
|
play(progress, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
private void play(final double progress, boolean earpiece) throws IOException {
|
private void play(final double progress, boolean earpiece) throws IOException {
|
||||||
if (this.mediaPlayer != null) { stop(); }
|
if (this.mediaPlayer != null) { stop(); }
|
||||||
|
|
||||||
LoadControl loadControl = new DefaultLoadControl.Builder().setBufferDurationsMs(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE).createDefaultLoadControl();
|
this.mediaPlayer = new ExoPlayer.Builder(context).build();
|
||||||
this.mediaPlayer = ExoPlayerFactory.newSimpleInstance(context, new DefaultRenderersFactory(context), new DefaultTrackSelector(), loadControl);
|
|
||||||
this.audioAttachmentServer = new AttachmentServer(context, slide.asAttachment());
|
this.audioAttachmentServer = new AttachmentServer(context, slide.asAttachment());
|
||||||
this.startTime = System.currentTimeMillis();
|
this.startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
audioAttachmentServer.start();
|
audioAttachmentServer.start();
|
||||||
|
|
||||||
mediaPlayer.prepare(createMediaSource(audioAttachmentServer.getUri()));
|
MediaItem mediaItem = MediaItem.fromUri(audioAttachmentServer.getUri());
|
||||||
mediaPlayer.setPlayWhenReady(true);
|
mediaPlayer.setMediaItem(mediaItem);
|
||||||
|
|
||||||
mediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
|
mediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
|
||||||
.setContentType(earpiece ? C.CONTENT_TYPE_SPEECH : C.CONTENT_TYPE_MUSIC)
|
.setContentType(earpiece ? C.AUDIO_CONTENT_TYPE_SPEECH : C.AUDIO_CONTENT_TYPE_MUSIC)
|
||||||
.setUsage(earpiece ? C.USAGE_VOICE_COMMUNICATION : C.USAGE_MEDIA)
|
.setUsage(earpiece ? C.USAGE_VOICE_COMMUNICATION : C.USAGE_MEDIA)
|
||||||
.build());
|
.build(),
|
||||||
mediaPlayer.addListener(new Player.EventListener() {
|
!earpiece);
|
||||||
|
mediaPlayer.addListener(new Player.Listener() {
|
||||||
|
|
||||||
boolean started = false;
|
boolean started = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
public void onPlaybackStateChanged(int playbackState) {
|
||||||
Log.d(TAG, "onPlayerStateChanged(" + playWhenReady + ", " + playbackState + ")");
|
Log.d(TAG, "onPlaybackStateChanged(" + playbackState + ")");
|
||||||
switch (playbackState) {
|
switch (playbackState) {
|
||||||
case Player.STATE_READY:
|
case Player.STATE_READY:
|
||||||
Log.i(TAG, "onPrepared() " + mediaPlayer.getBufferedPercentage() + "% buffered");
|
Log.i(TAG, "onPrepared() " + mediaPlayer.getBufferedPercentage() + "% buffered");
|
||||||
@ -166,9 +155,7 @@ public class AudioSlidePlayer implements SensorEventListener {
|
|||||||
sensorManager.unregisterListener(AudioSlidePlayer.this);
|
sensorManager.unregisterListener(AudioSlidePlayer.this);
|
||||||
|
|
||||||
if (wakeLock != null && wakeLock.isHeld()) {
|
if (wakeLock != null && wakeLock.isHeld()) {
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
||||||
wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,8 +165,9 @@ public class AudioSlidePlayer implements SensorEventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException error) {
|
public void onPlayerError(PlaybackException error) {
|
||||||
Log.w(TAG, "MediaPlayer Error: " + error);
|
Log.w(TAG, "MediaPlayer Error: " + error);
|
||||||
|
|
||||||
synchronized (AudioSlidePlayer.this) {
|
synchronized (AudioSlidePlayer.this) {
|
||||||
@ -193,9 +181,7 @@ public class AudioSlidePlayer implements SensorEventListener {
|
|||||||
sensorManager.unregisterListener(AudioSlidePlayer.this);
|
sensorManager.unregisterListener(AudioSlidePlayer.this);
|
||||||
|
|
||||||
if (wakeLock != null && wakeLock.isHeld()) {
|
if (wakeLock != null && wakeLock.isHeld()) {
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
||||||
wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,12 +189,9 @@ public class AudioSlidePlayer implements SensorEventListener {
|
|||||||
progressEventHandler.removeMessages(0);
|
progressEventHandler.removeMessages(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
private MediaSource createMediaSource(@NonNull Uri uri) {
|
mediaPlayer.prepare();
|
||||||
return new ExtractorMediaSource.Factory(new DefaultDataSourceFactory(context, BuildConfig.USER_AGENT))
|
mediaPlayer.setPlayWhenReady(true);
|
||||||
.setExtractorsFactory(new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true))
|
|
||||||
.createMediaSource(uri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void stop() {
|
public synchronized void stop() {
|
||||||
@ -340,14 +323,18 @@ public class AudioSlidePlayer implements SensorEventListener {
|
|||||||
|
|
||||||
int streamType;
|
int streamType;
|
||||||
|
|
||||||
if (event.values[0] < 5f && event.values[0] != proximitySensor.getMaximumRange()) {
|
if (
|
||||||
streamType = AudioManager.STREAM_VOICE_CALL;
|
proximitySensor != null &&
|
||||||
|
event.values[0] < 5f &&
|
||||||
|
event.values[0] != proximitySensor.getMaximumRange()
|
||||||
|
) {
|
||||||
|
streamType = C.AUDIO_CONTENT_TYPE_SPEECH;
|
||||||
} else {
|
} else {
|
||||||
streamType = AudioManager.STREAM_MUSIC;
|
streamType = C.AUDIO_CONTENT_TYPE_MUSIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamType == AudioManager.STREAM_VOICE_CALL &&
|
if (streamType == C.AUDIO_CONTENT_TYPE_SPEECH &&
|
||||||
mediaPlayer.getAudioStreamType() != streamType &&
|
mediaPlayer.getAudioAttributes().contentType != streamType &&
|
||||||
!audioManager.isWiredHeadsetOn())
|
!audioManager.isWiredHeadsetOn())
|
||||||
{
|
{
|
||||||
double position = mediaPlayer.getCurrentPosition();
|
double position = mediaPlayer.getCurrentPosition();
|
||||||
@ -361,11 +348,11 @@ public class AudioSlidePlayer implements SensorEventListener {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
} else if (streamType == AudioManager.STREAM_MUSIC &&
|
} else if (streamType == C.AUDIO_CONTENT_TYPE_MUSIC &&
|
||||||
mediaPlayer.getAudioStreamType() != streamType &&
|
mediaPlayer.getAudioAttributes().contentType != streamType &&
|
||||||
System.currentTimeMillis() - startTime > 500)
|
System.currentTimeMillis() - startTime > 500)
|
||||||
{
|
{
|
||||||
if (wakeLock != null) wakeLock.release();
|
if (wakeLock != null && wakeLock.isHeld()) wakeLock.release();
|
||||||
stop();
|
stop();
|
||||||
notifyOnStop();
|
notifyOnStop();
|
||||||
}
|
}
|
||||||
@ -403,7 +390,7 @@ public class AudioSlidePlayer implements SensorEventListener {
|
|||||||
sendEmptyMessageDelayed(0, 50);
|
sendEmptyMessageDelayed(0, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPlayerActive(@NonNull SimpleExoPlayer player) {
|
private boolean isPlayerActive(@NonNull ExoPlayer player) {
|
||||||
return player.getPlaybackState() == Player.STATE_READY || player.getPlaybackState() == Player.STATE_BUFFERING;
|
return player.getPlaybackState() == Player.STATE_READY || player.getPlaybackState() == Player.STATE_BUFFERING;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.avatar;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.provider.MediaStore;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
import com.theartofdev.edmodo.cropper.CropImage;
|
|
||||||
import com.theartofdev.edmodo.cropper.CropImageView;
|
|
||||||
|
|
||||||
import org.session.libsignal.utilities.NoExternalStorageException;
|
|
||||||
import org.session.libsignal.utilities.Log;
|
|
||||||
import org.session.libsignal.utilities.ExternalStorageUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.FileProviderUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
import static android.provider.MediaStore.EXTRA_OUTPUT;
|
|
||||||
|
|
||||||
public final class AvatarSelection {
|
|
||||||
|
|
||||||
private static final String TAG = AvatarSelection.class.getSimpleName();
|
|
||||||
|
|
||||||
public static final int REQUEST_CODE_CROP_IMAGE = CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE;
|
|
||||||
public static final int REQUEST_CODE_AVATAR = REQUEST_CODE_CROP_IMAGE + 1;
|
|
||||||
|
|
||||||
private AvatarSelection() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns result on {@link #REQUEST_CODE_CROP_IMAGE}
|
|
||||||
*/
|
|
||||||
public static void circularCropImage(Activity activity, Uri inputFile, Uri outputFile, @StringRes int title) {
|
|
||||||
CropImage.activity(inputFile)
|
|
||||||
.setGuidelines(CropImageView.Guidelines.ON)
|
|
||||||
.setAspectRatio(1, 1)
|
|
||||||
.setCropShape(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? CropImageView.CropShape.RECTANGLE : CropImageView.CropShape.OVAL)
|
|
||||||
.setOutputUri(outputFile)
|
|
||||||
.setAllowRotation(true)
|
|
||||||
.setAllowFlipping(true)
|
|
||||||
.setBackgroundColor(ContextCompat.getColor(activity, R.color.avatar_background))
|
|
||||||
.setActivityTitle(activity.getString(title))
|
|
||||||
.start(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri getResultUri(Intent data) {
|
|
||||||
return CropImage.getActivityResult(data).getUri();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns result on {@link #REQUEST_CODE_AVATAR}
|
|
||||||
*
|
|
||||||
* @return Temporary capture file if created.
|
|
||||||
*/
|
|
||||||
public static File startAvatarSelection(Activity activity, boolean includeClear, boolean attemptToIncludeCamera) {
|
|
||||||
File captureFile = null;
|
|
||||||
boolean hasCameraPermission = ContextCompat
|
|
||||||
.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
|
|
||||||
if (attemptToIncludeCamera && hasCameraPermission) {
|
|
||||||
try {
|
|
||||||
captureFile = File.createTempFile("avatar-capture", ".jpg", ExternalStorageUtil.getImageDir(activity));
|
|
||||||
} catch (IOException | NoExternalStorageException e) {
|
|
||||||
Log.e("Cannot reserve a temporary avatar capture file.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear);
|
|
||||||
activity.startActivityForResult(chooserIntent, REQUEST_CODE_AVATAR);
|
|
||||||
return captureFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Intent createAvatarSelectionIntent(Context context, @Nullable File tempCaptureFile, boolean includeClear) {
|
|
||||||
List<Intent> extraIntents = new LinkedList<>();
|
|
||||||
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
|
|
||||||
galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
|
|
||||||
|
|
||||||
if (!IntentUtils.isResolvable(context, galleryIntent)) {
|
|
||||||
galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
galleryIntent.setType("image/*");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tempCaptureFile != null) {
|
|
||||||
Uri uri = FileProviderUtil.getUriFor(context, tempCaptureFile);
|
|
||||||
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
||||||
cameraIntent.putExtra(EXTRA_OUTPUT, uri);
|
|
||||||
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
|
||||||
extraIntents.add(cameraIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (includeClear) {
|
|
||||||
extraIntents.add(new Intent("network.loki.securesms.action.CLEAR_PROFILE_PHOTO"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent chooserIntent = Intent.createChooser(galleryIntent, context.getString(R.string.profileDisplayPicture));
|
|
||||||
|
|
||||||
if (!extraIntents.isEmpty()) {
|
|
||||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Intent[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return chooserIntent;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,141 @@
|
|||||||
|
package org.thoughtcrime.securesms.avatar
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.canhub.cropper.CropImageContractOptions
|
||||||
|
import com.canhub.cropper.CropImageOptions
|
||||||
|
import com.canhub.cropper.CropImageView
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.session.libsession.utilities.getColorFromAttr
|
||||||
|
import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.session.libsignal.utilities.NoExternalStorageException
|
||||||
|
import org.thoughtcrime.securesms.util.FileProviderUtil
|
||||||
|
import org.thoughtcrime.securesms.util.IntentUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
class AvatarSelection(
|
||||||
|
private val activity: Activity,
|
||||||
|
private val onAvatarCropped: ActivityResultLauncher<CropImageContractOptions>,
|
||||||
|
private val onPickImage: ActivityResultLauncher<Intent>
|
||||||
|
) {
|
||||||
|
private val TAG: String = AvatarSelection::class.java.simpleName
|
||||||
|
|
||||||
|
private val bgColor by lazy { activity.getColorFromAttr(android.R.attr.colorPrimary) }
|
||||||
|
private val txtColor by lazy { activity.getColorFromAttr(android.R.attr.textColorPrimary) }
|
||||||
|
private val imageScrim by lazy { ContextCompat.getColor(activity, R.color.avatar_background) }
|
||||||
|
private val activityTitle by lazy { activity.getString(R.string.CropImageActivity_profile_avatar) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns result on [.REQUEST_CODE_CROP_IMAGE]
|
||||||
|
*/
|
||||||
|
fun circularCropImage(
|
||||||
|
inputFile: Uri?,
|
||||||
|
outputFile: Uri?
|
||||||
|
) {
|
||||||
|
onAvatarCropped.launch(
|
||||||
|
CropImageContractOptions(
|
||||||
|
uri = inputFile,
|
||||||
|
cropImageOptions = CropImageOptions(
|
||||||
|
guidelines = CropImageView.Guidelines.ON,
|
||||||
|
aspectRatioX = 1,
|
||||||
|
aspectRatioY = 1,
|
||||||
|
fixAspectRatio = true,
|
||||||
|
cropShape = CropImageView.CropShape.OVAL,
|
||||||
|
customOutputUri = outputFile,
|
||||||
|
allowRotation = true,
|
||||||
|
allowFlipping = true,
|
||||||
|
backgroundColor = imageScrim,
|
||||||
|
toolbarColor = bgColor,
|
||||||
|
activityBackgroundColor = bgColor,
|
||||||
|
toolbarTintColor = txtColor,
|
||||||
|
toolbarBackButtonColor = txtColor,
|
||||||
|
toolbarTitleColor = txtColor,
|
||||||
|
activityMenuIconColor = txtColor,
|
||||||
|
activityMenuTextColor = txtColor,
|
||||||
|
activityTitle = activityTitle
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns result on [.REQUEST_CODE_AVATAR]
|
||||||
|
*
|
||||||
|
* @return Temporary capture file if created.
|
||||||
|
*/
|
||||||
|
fun startAvatarSelection(
|
||||||
|
includeClear: Boolean,
|
||||||
|
attemptToIncludeCamera: Boolean
|
||||||
|
): File? {
|
||||||
|
var captureFile: File? = null
|
||||||
|
val hasCameraPermission = ContextCompat
|
||||||
|
.checkSelfPermission(
|
||||||
|
activity,
|
||||||
|
Manifest.permission.CAMERA
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
if (attemptToIncludeCamera && hasCameraPermission) {
|
||||||
|
try {
|
||||||
|
captureFile = File.createTempFile("avatar-capture", ".jpg", getImageDir(activity))
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e("Cannot reserve a temporary avatar capture file.", e)
|
||||||
|
} catch (e: NoExternalStorageException) {
|
||||||
|
Log.e("Cannot reserve a temporary avatar capture file.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear)
|
||||||
|
onPickImage.launch(chooserIntent)
|
||||||
|
return captureFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAvatarSelectionIntent(
|
||||||
|
context: Context,
|
||||||
|
tempCaptureFile: File?,
|
||||||
|
includeClear: Boolean
|
||||||
|
): Intent {
|
||||||
|
val extraIntents: MutableList<Intent> = LinkedList()
|
||||||
|
var galleryIntent = Intent(Intent.ACTION_PICK)
|
||||||
|
galleryIntent.setDataAndType(MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*")
|
||||||
|
|
||||||
|
if (!IntentUtils.isResolvable(context, galleryIntent)) {
|
||||||
|
galleryIntent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
|
galleryIntent.setType("image/*")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempCaptureFile != null) {
|
||||||
|
val uri = FileProviderUtil.getUriFor(context, tempCaptureFile)
|
||||||
|
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
||||||
|
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
|
||||||
|
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
extraIntents.add(cameraIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeClear) {
|
||||||
|
extraIntents.add(Intent("network.loki.securesms.action.CLEAR_PROFILE_PHOTO"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val chooserIntent = Intent.createChooser(
|
||||||
|
galleryIntent,
|
||||||
|
context.getString(R.string.CreateProfileActivity_profile_photo)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!extraIntents.isEmpty()) {
|
||||||
|
chooserIntent.putExtra(
|
||||||
|
Intent.EXTRA_INITIAL_INTENTS,
|
||||||
|
extraIntents.toTypedArray<Intent>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chooserIntent
|
||||||
|
}
|
||||||
|
}
|
@ -6,13 +6,9 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.graphics.Outline
|
import android.graphics.Outline
|
||||||
import android.hardware.Sensor
|
|
||||||
import android.hardware.SensorEvent
|
|
||||||
import android.hardware.SensorManager
|
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewOutlineProvider
|
import android.view.ViewOutlineProvider
|
||||||
@ -23,8 +19,6 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
||||||
import com.squareup.phrase.Phrase
|
import com.squareup.phrase.Phrase
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -34,7 +28,6 @@ import kotlinx.coroutines.launch
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ActivityWebrtcBinding
|
import network.loki.messenger.databinding.ActivityWebrtcBinding
|
||||||
import org.apache.commons.lang3.time.DurationFormatUtils
|
import org.apache.commons.lang3.time.DurationFormatUtils
|
||||||
import org.session.libsession.avatars.ProfileContactPhoto
|
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
|
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
@ -44,7 +37,6 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
|||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.service.WebRtcCallService
|
import org.thoughtcrime.securesms.service.WebRtcCallService
|
||||||
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
|
|
||||||
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
|
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
|
||||||
import org.thoughtcrime.securesms.webrtc.CallViewModel
|
import org.thoughtcrime.securesms.webrtc.CallViewModel
|
||||||
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_CONNECTED
|
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_CONNECTED
|
||||||
@ -56,7 +48,6 @@ import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_RINGING
|
|||||||
import org.thoughtcrime.securesms.webrtc.Orientation
|
import org.thoughtcrime.securesms.webrtc.Orientation
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.EARPIECE
|
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.EARPIECE
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.SPEAKER_PHONE
|
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.SPEAKER_PHONE
|
||||||
import kotlin.math.asin
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
||||||
@ -73,7 +64,6 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val viewModel by viewModels<CallViewModel>()
|
private val viewModel by viewModels<CallViewModel>()
|
||||||
private val glide by lazy { Glide.with(this) }
|
|
||||||
private lateinit var binding: ActivityWebrtcBinding
|
private lateinit var binding: ActivityWebrtcBinding
|
||||||
private var uiJob: Job? = null
|
private var uiJob: Job? = null
|
||||||
private var wantsToAnswer = false
|
private var wantsToAnswer = false
|
||||||
|
@ -1,198 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.components;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Outline;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.provider.ContactsContract;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewOutlineProvider;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.widget.AppCompatImageView;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|
||||||
|
|
||||||
import org.session.libsession.avatars.ContactColors;
|
|
||||||
import org.session.libsession.avatars.ContactPhoto;
|
|
||||||
import org.session.libsession.avatars.ResourceContactPhoto;
|
|
||||||
import org.session.libsession.utilities.Address;
|
|
||||||
import org.session.libsession.utilities.ThemeUtil;
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
|
||||||
import org.session.libsession.utilities.recipients.RecipientExporter;
|
|
||||||
import com.bumptech.glide.Glide;
|
|
||||||
import com.bumptech.glide.RequestManager;
|
|
||||||
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
public class AvatarImageView extends AppCompatImageView {
|
|
||||||
|
|
||||||
private static final String TAG = AvatarImageView.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final Paint LIGHT_THEME_OUTLINE_PAINT = new Paint();
|
|
||||||
private static final Paint DARK_THEME_OUTLINE_PAINT = new Paint();
|
|
||||||
|
|
||||||
static {
|
|
||||||
LIGHT_THEME_OUTLINE_PAINT.setColor(Color.argb((int) (255 * 0.2), 0, 0, 0));
|
|
||||||
LIGHT_THEME_OUTLINE_PAINT.setStyle(Paint.Style.STROKE);
|
|
||||||
LIGHT_THEME_OUTLINE_PAINT.setStrokeWidth(1f);
|
|
||||||
LIGHT_THEME_OUTLINE_PAINT.setAntiAlias(true);
|
|
||||||
|
|
||||||
DARK_THEME_OUTLINE_PAINT.setColor(Color.argb((int) (255 * 0.2), 255, 255, 255));
|
|
||||||
DARK_THEME_OUTLINE_PAINT.setStyle(Paint.Style.STROKE);
|
|
||||||
DARK_THEME_OUTLINE_PAINT.setStrokeWidth(1f);
|
|
||||||
DARK_THEME_OUTLINE_PAINT.setAntiAlias(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean inverted;
|
|
||||||
private Paint outlinePaint;
|
|
||||||
private OnClickListener listener;
|
|
||||||
|
|
||||||
private @Nullable RecipientContactPhoto recipientContactPhoto;
|
|
||||||
private @NonNull Drawable unknownRecipientDrawable;
|
|
||||||
|
|
||||||
public AvatarImageView(Context context) {
|
|
||||||
super(context);
|
|
||||||
initialize(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvatarImageView(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
initialize(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) {
|
|
||||||
setScaleType(ScaleType.CENTER_CROP);
|
|
||||||
|
|
||||||
if (attrs != null) {
|
|
||||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0);
|
|
||||||
inverted = typedArray.getBoolean(0, false);
|
|
||||||
typedArray.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
outlinePaint = ThemeUtil.isDarkTheme(getContext()) ? DARK_THEME_OUTLINE_PAINT : LIGHT_THEME_OUTLINE_PAINT;
|
|
||||||
setOutlineProvider(new ViewOutlineProvider() {
|
|
||||||
@Override
|
|
||||||
public void getOutline(View view, Outline outline) {
|
|
||||||
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setClipToOutline(true);
|
|
||||||
|
|
||||||
unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_default).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR.toConversationColor(getContext()), inverted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas canvas) {
|
|
||||||
super.onDraw(canvas);
|
|
||||||
|
|
||||||
float width = getWidth() - getPaddingRight() - getPaddingLeft();
|
|
||||||
float height = getHeight() - getPaddingBottom() - getPaddingTop();
|
|
||||||
float cx = width / 2f;
|
|
||||||
float cy = height / 2f;
|
|
||||||
float radius = Math.min(cx, cy) - (outlinePaint.getStrokeWidth() / 2f);
|
|
||||||
|
|
||||||
canvas.translate(getPaddingLeft(), getPaddingTop());
|
|
||||||
canvas.drawCircle(cx, cy, radius, outlinePaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOnClickListener(OnClickListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
super.setOnClickListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(String hexEncodedPublicKey) {
|
|
||||||
Address address = Address.fromSerialized(hexEncodedPublicKey);
|
|
||||||
Recipient recipient = Recipient.from(getContext(), address, false);
|
|
||||||
updateAvatar(recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateAvatar(Recipient recipient) {
|
|
||||||
setAvatar(Glide.with(getContext()), recipient, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAvatar(@NonNull RequestManager requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) {
|
|
||||||
if (recipient != null) {
|
|
||||||
if (recipient.isLocalNumber()) {
|
|
||||||
setImageDrawable(new ResourceContactPhoto(R.drawable.ic_note_to_self).asDrawable(getContext(), recipient.getColor().toAvatarColor(getContext()), inverted));
|
|
||||||
} else {
|
|
||||||
RecipientContactPhoto photo = new RecipientContactPhoto(recipient);
|
|
||||||
if (!photo.equals(recipientContactPhoto)) {
|
|
||||||
requestManager.clear(this);
|
|
||||||
recipientContactPhoto = photo;
|
|
||||||
|
|
||||||
Drawable photoPlaceholderDrawable = AvatarPlaceholderGenerator.generate(
|
|
||||||
getContext(), 128, recipient.getAddress().serialize(), recipient.getName());
|
|
||||||
|
|
||||||
if (photo.contactPhoto != null) {
|
|
||||||
requestManager.load(photo.contactPhoto)
|
|
||||||
.fallback(photoPlaceholderDrawable)
|
|
||||||
.error(photoPlaceholderDrawable)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.circleCrop()
|
|
||||||
.into(this);
|
|
||||||
} else {
|
|
||||||
requestManager.load(photoPlaceholderDrawable)
|
|
||||||
.circleCrop()
|
|
||||||
.into(this);
|
|
||||||
// setImageDrawable(photoPlaceholderDrawable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
recipientContactPhoto = null;
|
|
||||||
requestManager.clear(this);
|
|
||||||
setImageDrawable(unknownRecipientDrawable);
|
|
||||||
super.setOnClickListener(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear(@NonNull RequestManager glideRequests) {
|
|
||||||
glideRequests.clear(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setAvatarClickHandler(final Recipient recipient, boolean quickContactEnabled) {
|
|
||||||
if (!recipient.isGroupRecipient() && quickContactEnabled) {
|
|
||||||
super.setOnClickListener(v -> {
|
|
||||||
if (recipient.getContactUri() != null) {
|
|
||||||
ContactsContract.QuickContact.showQuickContact(getContext(), AvatarImageView.this, recipient.getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null);
|
|
||||||
} else {
|
|
||||||
getContext().startActivity(RecipientExporter.export(recipient).asAddContactIntent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
super.setOnClickListener(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RecipientContactPhoto {
|
|
||||||
|
|
||||||
private final @NonNull Recipient recipient;
|
|
||||||
private final @Nullable ContactPhoto contactPhoto;
|
|
||||||
private final boolean ready;
|
|
||||||
|
|
||||||
RecipientContactPhoto(@NonNull Recipient recipient) {
|
|
||||||
this.recipient = recipient;
|
|
||||||
this.ready = !recipient.isResolving();
|
|
||||||
this.contactPhoto = recipient.getContactPhoto();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals(@Nullable RecipientContactPhoto other) {
|
|
||||||
if (other == null) return false;
|
|
||||||
|
|
||||||
return other.recipient.equals(recipient) &&
|
|
||||||
other.recipient.getColor().equals(recipient.getColor()) &&
|
|
||||||
other.ready == ready &&
|
|
||||||
Objects.equals(other.contactPhoto, contactPhoto);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -38,10 +38,13 @@ class ProfilePictureView @JvmOverloads constructor(
|
|||||||
var additionalDisplayName: String? = null
|
var additionalDisplayName: String? = null
|
||||||
|
|
||||||
private val profilePicturesCache = mutableMapOf<View, Recipient>()
|
private val profilePicturesCache = mutableMapOf<View, Recipient>()
|
||||||
|
private val resourcePadding by lazy {
|
||||||
|
context.resources.getDimensionPixelSize(R.dimen.normal_padding).toFloat()
|
||||||
|
}
|
||||||
private val unknownRecipientDrawable by lazy { ResourceContactPhoto(R.drawable.ic_profile_default)
|
private val unknownRecipientDrawable by lazy { ResourceContactPhoto(R.drawable.ic_profile_default)
|
||||||
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) }
|
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) }
|
||||||
private val unknownOpenGroupDrawable by lazy { ResourceContactPhoto(R.drawable.ic_notification)
|
private val unknownOpenGroupDrawable by lazy { ResourceContactPhoto(R.drawable.ic_notification)
|
||||||
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) }
|
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding) }
|
||||||
|
|
||||||
constructor(context: Context, sender: Recipient): this(context) {
|
constructor(context: Context, sender: Recipient): this(context) {
|
||||||
update(sender)
|
update(sender)
|
||||||
|
@ -868,9 +868,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
viewModel.saveDraft(binding.inputBar.text.trim())
|
if(::binding.isInitialized) {
|
||||||
cancelVoiceMessage()
|
viewModel.saveDraft(binding.inputBar.text.trim())
|
||||||
tearDownRecipientObserver()
|
cancelVoiceMessage()
|
||||||
|
tearDownRecipientObserver()
|
||||||
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -536,7 +536,7 @@ open class Storage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateConvoVolatile(convos: ConversationVolatileConfig, messageTimestamp: Long) {
|
private fun updateConvoVolatile(convos: ConversationVolatileConfig, messageTimestamp: Long) {
|
||||||
val extracted = convos.all()
|
val extracted = convos.all().filterNotNull()
|
||||||
for (conversation in extracted) {
|
for (conversation in extracted) {
|
||||||
val threadId = when (conversation) {
|
val threadId = when (conversation) {
|
||||||
is Conversation.OneToOne -> getThreadIdFor(conversation.accountId, null, null, createThread = false)
|
is Conversation.OneToOne -> getThreadIdFor(conversation.accountId, null, null, createThread = false)
|
||||||
|
@ -4,7 +4,10 @@ import android.net.Uri;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -16,6 +19,7 @@ import org.thoughtcrime.securesms.video.VideoPlayer;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public class MediaSendVideoFragment extends Fragment implements MediaSendPageFragment {
|
public class MediaSendVideoFragment extends Fragment implements MediaSendPageFragment {
|
||||||
|
|
||||||
private static final String TAG = MediaSendVideoFragment.class.getSimpleName();
|
private static final String TAG = MediaSendVideoFragment.class.getSimpleName();
|
||||||
|
@ -28,7 +28,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|||||||
|
|
||||||
import org.session.libsession.avatars.ContactColors;
|
import org.session.libsession.avatars.ContactColors;
|
||||||
import org.session.libsession.avatars.ContactPhoto;
|
import org.session.libsession.avatars.ContactPhoto;
|
||||||
import org.session.libsession.avatars.GeneratedContactPhoto;
|
import org.session.libsession.avatars.ResourceContactPhoto;
|
||||||
import org.session.libsession.messaging.contacts.Contact;
|
import org.session.libsession.messaging.contacts.Contact;
|
||||||
import org.session.libsession.utilities.NotificationPrivacyPreference;
|
import org.session.libsession.utilities.NotificationPrivacyPreference;
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
@ -60,6 +60,8 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
|||||||
private CharSequence contentTitle;
|
private CharSequence contentTitle;
|
||||||
private CharSequence contentText;
|
private CharSequence contentText;
|
||||||
|
|
||||||
|
private static final Integer ICON_SIZE = 128;
|
||||||
|
|
||||||
public SingleRecipientNotificationBuilder(@NonNull Context context, @NonNull NotificationPrivacyPreference privacy)
|
public SingleRecipientNotificationBuilder(@NonNull Context context, @NonNull NotificationPrivacyPreference privacy)
|
||||||
{
|
{
|
||||||
super(context, privacy);
|
super(context, privacy);
|
||||||
@ -108,7 +110,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
setContentTitle(context.getString(R.string.app_name));
|
setContentTitle(context.getString(R.string.app_name));
|
||||||
setLargeIcon(new GeneratedContactPhoto("Unknown", R.drawable.ic_profile_default).asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context)));
|
setLargeIcon(AvatarPlaceholderGenerator.generate(context, ICON_SIZE, "", "Unknown"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,7 +332,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
|||||||
private static Drawable getPlaceholderDrawable(Context context, Recipient recipient) {
|
private static Drawable getPlaceholderDrawable(Context context, Recipient recipient) {
|
||||||
String publicKey = recipient.getAddress().serialize();
|
String publicKey = recipient.getAddress().serialize();
|
||||||
String displayName = recipient.getName();
|
String displayName = recipient.getName();
|
||||||
return AvatarPlaceholderGenerator.generate(context, 128, publicKey, displayName);
|
return AvatarPlaceholderGenerator.generate(context, ICON_SIZE, publicKey, displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,6 +18,7 @@ import android.view.View
|
|||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -34,6 +35,8 @@ import androidx.core.view.isInvisible
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import com.canhub.cropper.CropImage
|
||||||
|
import com.canhub.cropper.CropImageContract
|
||||||
import com.squareup.phrase.Phrase
|
import com.squareup.phrase.Phrase
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -111,6 +114,48 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
private val hexEncodedPublicKey: String get() = TextSecurePreferences.getLocalNumber(this)!!
|
private val hexEncodedPublicKey: String get() = TextSecurePreferences.getLocalNumber(this)!!
|
||||||
|
|
||||||
|
private val onAvatarCropped = registerForActivityResult(CropImageContract()) { result ->
|
||||||
|
when {
|
||||||
|
result.isSuccessful -> {
|
||||||
|
Log.i(TAG, result.getUriFilePath(this).toString())
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val profilePictureToBeUploaded =
|
||||||
|
BitmapUtil.createScaledBytes(
|
||||||
|
this@SettingsActivity,
|
||||||
|
result.getUriFilePath(this@SettingsActivity).toString(),
|
||||||
|
ProfileMediaConstraints()
|
||||||
|
).bitmap
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
updateProfilePicture(profilePictureToBeUploaded)
|
||||||
|
}
|
||||||
|
} catch (e: BitmapDecodingException) {
|
||||||
|
Log.e(TAG, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result is CropImage.CancelledResult -> {
|
||||||
|
Log.i(TAG, "Cropping image was cancelled by the user")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.e(TAG, "Cropping image failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onPickImage = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
){ result ->
|
||||||
|
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||||
|
|
||||||
|
val outputFile = Uri.fromFile(File(cacheDir, "cropped"))
|
||||||
|
val inputFile: Uri? = result.data?.data ?: tempFile?.let(Uri::fromFile)
|
||||||
|
cropImage(inputFile, outputFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val avatarSelection = AvatarSelection(this, onAvatarCropped, onPickImage)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SCROLL_STATE = "SCROLL_STATE"
|
private const val SCROLL_STATE = "SCROLL_STATE"
|
||||||
}
|
}
|
||||||
@ -186,32 +231,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
if (resultCode != Activity.RESULT_OK) return
|
|
||||||
when (requestCode) {
|
|
||||||
AvatarSelection.REQUEST_CODE_AVATAR -> {
|
|
||||||
val outputFile = Uri.fromFile(File(cacheDir, "cropped"))
|
|
||||||
val inputFile: Uri? = data?.data ?: tempFile?.let(Uri::fromFile)
|
|
||||||
AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.photo)
|
|
||||||
}
|
|
||||||
|
|
||||||
AvatarSelection.REQUEST_CODE_CROP_IMAGE -> {
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
val profilePictureToBeUploaded = BitmapUtil.createScaledBytes(this@SettingsActivity, AvatarSelection.getResultUri(data), ProfileMediaConstraints()).bitmap
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
updateProfilePicture(profilePictureToBeUploaded)
|
|
||||||
}
|
|
||||||
} catch (e: BitmapDecodingException) {
|
|
||||||
Log.e(TAG, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
|
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
|
||||||
@ -417,10 +436,17 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
Permissions.with(this)
|
Permissions.with(this)
|
||||||
.request(Manifest.permission.CAMERA)
|
.request(Manifest.permission.CAMERA)
|
||||||
.onAnyResult {
|
.onAnyResult {
|
||||||
tempFile = AvatarSelection.startAvatarSelection(this, false, true)
|
tempFile = avatarSelection.startAvatarSelection( false, true)
|
||||||
}
|
}
|
||||||
.execute()
|
.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun cropImage(inputFile: Uri?, outputFile: Uri?){
|
||||||
|
avatarSelection.circularCropImage(
|
||||||
|
inputFile = inputFile,
|
||||||
|
outputFile = outputFile,
|
||||||
|
)
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {
|
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {
|
||||||
|
@ -18,56 +18,46 @@ package org.thoughtcrime.securesms.video;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.MediaController;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.widget.VideoView;
|
import android.widget.VideoView;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
import androidx.annotation.NonNull;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.LoadControl;
|
|
||||||
import com.google.android.exoplayer2.Player;
|
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
|
||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
|
||||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
|
||||||
import com.google.android.exoplayer2.ui.PlayerControlView;
|
|
||||||
import com.google.android.exoplayer2.ui.PlayerView;
|
|
||||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.attachments.AttachmentServer;
|
import androidx.media3.common.MediaItem;
|
||||||
|
import androidx.media3.common.Player;
|
||||||
|
import androidx.media3.common.AudioAttributes;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
|
import androidx.media3.ui.LegacyPlayerControlView;
|
||||||
|
import androidx.media3.ui.PlayerView;
|
||||||
|
|
||||||
|
|
||||||
|
import org.session.libsession.utilities.ViewUtil;
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
|
import org.thoughtcrime.securesms.attachments.AttachmentServer;
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||||
import org.session.libsession.utilities.ViewUtil;
|
|
||||||
import org.thoughtcrime.securesms.video.exo.AttachmentDataSourceFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
public class VideoPlayer extends FrameLayout {
|
public class VideoPlayer extends FrameLayout {
|
||||||
|
|
||||||
private static final String TAG = VideoPlayer.class.getSimpleName();
|
private static final String TAG = VideoPlayer.class.getSimpleName();
|
||||||
|
|
||||||
@Nullable private final VideoView videoView;
|
@Nullable private final VideoView videoView;
|
||||||
@Nullable private final PlayerView exoView;
|
@Nullable private final PlayerView exoView;
|
||||||
|
|
||||||
@Nullable private SimpleExoPlayer exoPlayer;
|
@Nullable private ExoPlayer exoPlayer;
|
||||||
@Nullable private PlayerControlView exoControls;
|
@Nullable private LegacyPlayerControlView exoControls;
|
||||||
@Nullable private AttachmentServer attachmentServer;
|
@Nullable private AttachmentServer attachmentServer;
|
||||||
@Nullable private Window window;
|
@Nullable private Window window;
|
||||||
|
|
||||||
@ -84,23 +74,16 @@ public class VideoPlayer extends FrameLayout {
|
|||||||
|
|
||||||
inflate(context, R.layout.video_player, this);
|
inflate(context, R.layout.video_player, this);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 16) {
|
this.exoView = ViewUtil.findById(this, R.id.video_view);
|
||||||
this.exoView = ViewUtil.findById(this, R.id.video_view);
|
this.videoView = null;
|
||||||
this.videoView = null;
|
this.exoControls = new LegacyPlayerControlView(getContext());
|
||||||
this.exoControls = new PlayerControlView(getContext());
|
this.exoControls.setShowTimeoutMs(-1);
|
||||||
this.exoControls.setShowTimeoutMs(-1);
|
|
||||||
} else {
|
|
||||||
this.videoView = ViewUtil.findById(this, R.id.video_view);
|
|
||||||
this.exoView = null;
|
|
||||||
initializeVideoViewControls(videoView);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay)
|
public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
if (Build.VERSION.SDK_INT >= 16) setExoViewSource(videoSource, autoplay);
|
setExoViewSource(videoSource, autoplay);
|
||||||
else setVideoViewSource(videoSource, autoplay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pause() {
|
public void pause() {
|
||||||
@ -141,25 +124,20 @@ public class VideoPlayer extends FrameLayout {
|
|||||||
private void setExoViewSource(@NonNull VideoSlide videoSource, boolean autoplay)
|
private void setExoViewSource(@NonNull VideoSlide videoSource, boolean autoplay)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
exoPlayer = new ExoPlayer.Builder(getContext()).build();
|
||||||
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
|
|
||||||
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
|
||||||
LoadControl loadControl = new DefaultLoadControl();
|
|
||||||
|
|
||||||
exoPlayer = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, loadControl);
|
|
||||||
exoPlayer.addListener(new ExoPlayerListener(window));
|
exoPlayer.addListener(new ExoPlayerListener(window));
|
||||||
|
exoPlayer.setAudioAttributes(AudioAttributes.DEFAULT, true);
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
exoView.setPlayer(exoPlayer);
|
exoView.setPlayer(exoPlayer);
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
exoControls.setPlayer(exoPlayer);
|
exoControls.setPlayer(exoPlayer);
|
||||||
|
|
||||||
DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(getContext(), "GenericUserAgent", null);
|
if(videoSource.getUri() != null){
|
||||||
AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(getContext(), defaultDataSourceFactory, null);
|
MediaItem mediaItem = MediaItem.fromUri(videoSource.getUri());
|
||||||
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
|
exoPlayer.setMediaItem(mediaItem);
|
||||||
|
}
|
||||||
|
|
||||||
MediaSource mediaSource = new ExtractorMediaSource(videoSource.getUri(), attachmentDataSourceFactory, extractorsFactory, null, null);
|
exoPlayer.prepare();
|
||||||
|
|
||||||
exoPlayer.prepare(mediaSource);
|
|
||||||
exoPlayer.setPlayWhenReady(autoplay);
|
exoPlayer.setPlayWhenReady(autoplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,15 +167,7 @@ public class VideoPlayer extends FrameLayout {
|
|||||||
if (autoplay) this.videoView.start();
|
if (autoplay) this.videoView.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeVideoViewControls(@NonNull VideoView videoView) {
|
private static class ExoPlayerListener implements Player.Listener {
|
||||||
MediaController mediaController = new MediaController(getContext());
|
|
||||||
mediaController.setAnchorView(videoView);
|
|
||||||
mediaController.setMediaPlayer(videoView);
|
|
||||||
|
|
||||||
videoView.setMediaController(mediaController);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ExoPlayerListener extends Player.DefaultEventListener {
|
|
||||||
private final Window window;
|
private final Window window;
|
||||||
|
|
||||||
ExoPlayerListener(Window window) {
|
ExoPlayerListener(Window window) {
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.video.exo;
|
|
||||||
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class AttachmentDataSource implements DataSource {
|
|
||||||
|
|
||||||
private final DefaultDataSource defaultDataSource;
|
|
||||||
private final PartDataSource partDataSource;
|
|
||||||
|
|
||||||
private DataSource dataSource;
|
|
||||||
|
|
||||||
public AttachmentDataSource(DefaultDataSource defaultDataSource, PartDataSource partDataSource) {
|
|
||||||
this.defaultDataSource = defaultDataSource;
|
|
||||||
this.partDataSource = partDataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addTransferListener(TransferListener transferListener) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long open(DataSpec dataSpec) throws IOException {
|
|
||||||
if (PartAuthority.isLocalUri(dataSpec.uri)) dataSource = partDataSource;
|
|
||||||
else dataSource = defaultDataSource;
|
|
||||||
|
|
||||||
return dataSource.open(dataSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] buffer, int offset, int readLength) throws IOException {
|
|
||||||
return dataSource.read(buffer, offset, readLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri getUri() {
|
|
||||||
return dataSource.getUri();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<String>> getResponseHeaders() {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
dataSource.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.video.exo;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
|
||||||
|
|
||||||
public class AttachmentDataSourceFactory implements DataSource.Factory {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
private final DefaultDataSourceFactory defaultDataSourceFactory;
|
|
||||||
private final TransferListener listener;
|
|
||||||
|
|
||||||
public AttachmentDataSourceFactory(@NonNull Context context,
|
|
||||||
@NonNull DefaultDataSourceFactory defaultDataSourceFactory,
|
|
||||||
@Nullable TransferListener listener)
|
|
||||||
{
|
|
||||||
this.context = context;
|
|
||||||
this.defaultDataSourceFactory = defaultDataSourceFactory;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AttachmentDataSource createDataSource() {
|
|
||||||
return new AttachmentDataSource(defaultDataSourceFactory.createDataSource(),
|
|
||||||
new PartDataSource(context, listener));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.video.exo;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
|
||||||
|
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
|
||||||
import org.thoughtcrime.securesms.mms.PartUriParser;
|
|
||||||
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class PartDataSource implements DataSource {
|
|
||||||
|
|
||||||
private final @NonNull Context context;
|
|
||||||
private final @Nullable TransferListener listener;
|
|
||||||
|
|
||||||
private Uri uri;
|
|
||||||
private InputStream inputSteam;
|
|
||||||
|
|
||||||
PartDataSource(@NonNull Context context, @Nullable TransferListener listener) {
|
|
||||||
this.context = context.getApplicationContext();
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addTransferListener(TransferListener transferListener) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long open(DataSpec dataSpec) throws IOException {
|
|
||||||
this.uri = dataSpec.uri;
|
|
||||||
|
|
||||||
AttachmentDatabase attachmentDatabase = DatabaseComponent.get(context).attachmentDatabase();
|
|
||||||
PartUriParser partUri = new PartUriParser(uri);
|
|
||||||
Attachment attachment = attachmentDatabase.getAttachment(partUri.getPartId());
|
|
||||||
|
|
||||||
if (attachment == null) throw new IOException("Attachment not found");
|
|
||||||
|
|
||||||
this.inputSteam = attachmentDatabase.getAttachmentStream(partUri.getPartId(), dataSpec.position);
|
|
||||||
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onTransferStart(this, dataSpec, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachment.getSize() - dataSpec.position <= 0) throw new EOFException("No more data");
|
|
||||||
|
|
||||||
return attachment.getSize() - dataSpec.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] buffer, int offset, int readLength) throws IOException {
|
|
||||||
int read = inputSteam.read(buffer, offset, readLength);
|
|
||||||
|
|
||||||
if (read > 0 && listener != null) {
|
|
||||||
listener.onBytesTransferred(this, null, false, read);
|
|
||||||
}
|
|
||||||
|
|
||||||
return read;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri getUri() {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<String>> getResponseHeaders() {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
inputSteam.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -63,6 +63,7 @@ class PeerConnectionWrapper(private val context: Context,
|
|||||||
val configuration = PeerConnection.RTCConfiguration(iceServers).apply {
|
val configuration = PeerConnection.RTCConfiguration(iceServers).apply {
|
||||||
bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE
|
bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE
|
||||||
rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE
|
rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE
|
||||||
|
sdpSemantics = PeerConnection.SdpSemantics.PLAN_B
|
||||||
if (relay) {
|
if (relay) {
|
||||||
iceTransportsType = PeerConnection.IceTransportsType.RELAY
|
iceTransportsType = PeerConnection.IceTransportsType.RELAY
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
<androidx.media3.ui.AspectRatioFrameLayout
|
||||||
android:id="@+id/exo_content_frame"
|
android:id="@+id/exo_content_frame"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.google.android.exoplayer2.ui.PlayerView
|
<androidx.media3.ui.PlayerView
|
||||||
android:id="@+id/video_view"
|
android:id="@+id/video_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -338,22 +338,6 @@
|
|||||||
<attr name="labeledEditText_textLayout" format="reference" />
|
<attr name="labeledEditText_textLayout" format="reference" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="WaveformSeekBar">
|
|
||||||
<attr name="progress" format="float"/>
|
|
||||||
<attr name="bar_width" format="dimension"/>
|
|
||||||
<attr name="bar_gap" format="dimension"/>
|
|
||||||
<attr name="bar_min_height" format="dimension"/>
|
|
||||||
<attr name="bar_corner_radius" format="dimension"/>
|
|
||||||
<attr name="bar_background_color" format="color"/>
|
|
||||||
<attr name="bar_progress_color" format="color"/>
|
|
||||||
<!-- Corresponds to WaveformSeekBar.WaveGravity enum. -->
|
|
||||||
<attr name="bar_gravity" format="enum">
|
|
||||||
<enum name="top" value="1" />
|
|
||||||
<enum name="center" value="2" />
|
|
||||||
<enum name="bottom" value="3" />
|
|
||||||
</attr>
|
|
||||||
</declare-styleable>
|
|
||||||
|
|
||||||
<declare-styleable name="KeyboardPageSearchView">
|
<declare-styleable name="KeyboardPageSearchView">
|
||||||
<attr name="show_always" format="boolean" />
|
<attr name="show_always" format="boolean" />
|
||||||
<attr name="search_bar_tint" format="color|reference" />
|
<attr name="search_bar_tint" format="color|reference" />
|
||||||
|
@ -18,10 +18,6 @@
|
|||||||
<item name="android:textSize">@dimen/very_large_font_size</item>
|
<item name="android:textSize">@dimen/very_large_font_size</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Session.SearchView" parent="@style/Widget.AppCompat.SearchView">
|
|
||||||
<item name="closeIcon">@drawable/ic_clear</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="ThemeOverlay.Session.AlertDialog" parent="ThemeOverlay.AppCompat.Dialog.Alert">
|
<style name="ThemeOverlay.Session.AlertDialog" parent="ThemeOverlay.AppCompat.Dialog.Alert">
|
||||||
<item name="android:windowBackground">@drawable/default_dialog_background</item>
|
<item name="android:windowBackground">@drawable/default_dialog_background</item>
|
||||||
<item name="android:colorBackground">?backgroundSecondary</item>
|
<item name="android:colorBackground">?backgroundSecondary</item>
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
<item name="android:colorControlNormal">?android:textColorPrimary</item>
|
<item name="android:colorControlNormal">?android:textColorPrimary</item>
|
||||||
<item name="conversation_menu_border_color">?colorDividerBackground</item>
|
<item name="conversation_menu_border_color">?colorDividerBackground</item>
|
||||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix.Settings</item>
|
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix.Settings</item>
|
||||||
<item name="searchViewStyle">@style/Widget.Session.SearchView</item>
|
|
||||||
<item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.MaterialComponents.SmallComponent</item>
|
<item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.MaterialComponents.SmallComponent</item>
|
||||||
<item name="elementBorderColor">?android:textColorSecondary</item>
|
<item name="elementBorderColor">?android:textColorSecondary</item>
|
||||||
<item name="colorOnSurface">?android:textColorPrimary</item>
|
<item name="colorOnSurface">?android:textColorPrimary</item>
|
||||||
|
@ -56,7 +56,6 @@ allprojects {
|
|||||||
includeGroupByRegex "org\\.signal.*"
|
includeGroupByRegex "org\\.signal.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jcenter()
|
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
if (project.hasProperty('huawei')) maven {
|
if (project.hasProperty('huawei')) maven {
|
||||||
url 'https://developer.huawei.com/repo/'
|
url 'https://developer.huawei.com/repo/'
|
||||||
|
@ -551,6 +551,7 @@ class InstrumentedTests {
|
|||||||
is Conversation.OneToOne -> seen.add("1-to-1: ${convo.accountId}")
|
is Conversation.OneToOne -> seen.add("1-to-1: ${convo.accountId}")
|
||||||
is Conversation.Community -> seen.add("og: ${convo.baseCommunityInfo.baseUrl}/r/${convo.baseCommunityInfo.room}")
|
is Conversation.Community -> seen.add("og: ${convo.baseCommunityInfo.baseUrl}/r/${convo.baseCommunityInfo.room}")
|
||||||
is Conversation.LegacyGroup -> seen.add("cl: ${convo.groupId}")
|
is Conversation.LegacyGroup -> seen.add("cl: ${convo.groupId}")
|
||||||
|
null -> TODO()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ class ConversationVolatileConfig(pointer: Long): ConfigBase(pointer) {
|
|||||||
external fun allOneToOnes(): List<Conversation.OneToOne>
|
external fun allOneToOnes(): List<Conversation.OneToOne>
|
||||||
external fun allCommunities(): List<Conversation.Community>
|
external fun allCommunities(): List<Conversation.Community>
|
||||||
external fun allLegacyClosedGroups(): List<Conversation.LegacyGroup>
|
external fun allLegacyClosedGroups(): List<Conversation.LegacyGroup>
|
||||||
external fun all(): List<Conversation>
|
external fun all(): List<Conversation?>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,6 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
implementation "com.github.bumptech.glide:glide:$glideVersion"
|
implementation "com.github.bumptech.glide:glide:$glideVersion"
|
||||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
|
||||||
implementation 'com.annimon:stream:1.1.8'
|
implementation 'com.annimon:stream:1.1.8'
|
||||||
implementation 'com.makeramen:roundedimageview:2.1.0'
|
implementation 'com.makeramen:roundedimageview:2.1.0'
|
||||||
implementation 'com.esotericsoftware:kryo:5.1.1'
|
implementation 'com.esotericsoftware:kryo:5.1.1'
|
||||||
|
@ -5,6 +5,6 @@ import android.graphics.drawable.Drawable;
|
|||||||
|
|
||||||
public interface FallbackContactPhoto {
|
public interface FallbackContactPhoto {
|
||||||
|
|
||||||
public Drawable asDrawable(Context context, int color);
|
|
||||||
public Drawable asDrawable(Context context, int color, boolean inverted);
|
public Drawable asDrawable(Context context, int color, boolean inverted);
|
||||||
|
public Drawable asDrawable(Context context, int color, boolean inverted, Float padding);
|
||||||
}
|
}
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
package org.session.libsession.avatars;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Typeface;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.graphics.drawable.LayerDrawable;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.amulyakhare.textdrawable.TextDrawable;
|
|
||||||
|
|
||||||
import org.session.libsession.R;
|
|
||||||
import org.session.libsession.utilities.ThemeUtil;
|
|
||||||
import org.session.libsession.utilities.ViewUtil;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class GeneratedContactPhoto implements FallbackContactPhoto {
|
|
||||||
|
|
||||||
private static final Pattern PATTERN = Pattern.compile("[^\\p{L}\\p{Nd}\\p{S}]+");
|
|
||||||
private static final Typeface TYPEFACE = Typeface.create("sans-serif-medium", Typeface.NORMAL);
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final int fallbackResId;
|
|
||||||
|
|
||||||
public GeneratedContactPhoto(@NonNull String name, @DrawableRes int fallbackResId) {
|
|
||||||
this.name = name;
|
|
||||||
this.fallbackResId = fallbackResId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Drawable asDrawable(Context context, int color) {
|
|
||||||
return asDrawable(context, color,false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Drawable asDrawable(Context context, int color, boolean inverted) {
|
|
||||||
int targetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
|
|
||||||
String character = getAbbreviation(name);
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(character)) {
|
|
||||||
Drawable base = TextDrawable.builder()
|
|
||||||
.beginConfig()
|
|
||||||
.width(targetSize)
|
|
||||||
.height(targetSize)
|
|
||||||
.useFont(TYPEFACE)
|
|
||||||
.fontSize(ViewUtil.dpToPx(context, 24))
|
|
||||||
.textColor(inverted ? color : Color.WHITE)
|
|
||||||
.endConfig()
|
|
||||||
.buildRound(character, inverted ? Color.WHITE : color);
|
|
||||||
|
|
||||||
Drawable gradient = context.getResources().getDrawable(ThemeUtil.isDarkTheme(context) ? R.drawable.avatar_gradient_dark
|
|
||||||
: R.drawable.avatar_gradient_light);
|
|
||||||
return new LayerDrawable(new Drawable[] { base, gradient });
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ResourceContactPhoto(fallbackResId).asDrawable(context, color, inverted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable String getAbbreviation(String name) {
|
|
||||||
String[] parts = name.split(" ");
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < parts.length && count < 2; i++) {
|
|
||||||
String cleaned = PATTERN.matcher(parts[i]).replaceFirst("");
|
|
||||||
if (!TextUtils.isEmpty(cleaned)) {
|
|
||||||
builder.appendCodePoint(cleaned.codePointAt(0));
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (builder.length() == 0) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,13 +4,13 @@ import android.content.Context;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
import android.graphics.drawable.LayerDrawable;
|
import android.graphics.drawable.LayerDrawable;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
|
||||||
import com.amulyakhare.textdrawable.TextDrawable;
|
|
||||||
import com.makeramen.roundedimageview.RoundedDrawable;
|
import com.makeramen.roundedimageview.RoundedDrawable;
|
||||||
|
|
||||||
import org.session.libsession.R;
|
import org.session.libsession.R;
|
||||||
@ -25,19 +25,33 @@ public class ResourceContactPhoto implements FallbackContactPhoto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Drawable asDrawable(Context context, int color) {
|
public Drawable asDrawable(Context context, int color, boolean inverted) {
|
||||||
return asDrawable(context, color, false);
|
return asDrawable(context, 0, false, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Drawable asDrawable(Context context, int color, boolean inverted) {
|
public Drawable asDrawable(Context context, int color, boolean inverted, Float padding) {
|
||||||
Drawable background = TextDrawable.builder().buildRound(" ", inverted ? Color.WHITE : color);
|
// rounded colored background
|
||||||
|
GradientDrawable background = new GradientDrawable();
|
||||||
|
background.setShape(GradientDrawable.OVAL);
|
||||||
|
background.setColor(inverted ? Color.WHITE : color);
|
||||||
|
|
||||||
|
// resource image in the foreground
|
||||||
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(AppCompatResources.getDrawable(context, resourceId));
|
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(AppCompatResources.getDrawable(context, resourceId));
|
||||||
|
|
||||||
foreground.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
if (foreground != null) {
|
||||||
|
if(padding == 0f){
|
||||||
|
foreground.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||||
|
} else {
|
||||||
|
// apply padding via a transparent border oterhwise things get misaligned
|
||||||
|
foreground.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||||
|
foreground.setBorderColor(Color.TRANSPARENT);
|
||||||
|
foreground.setBorderWidth(padding);
|
||||||
|
}
|
||||||
|
|
||||||
if (inverted) {
|
if (inverted) {
|
||||||
foreground.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
|
foreground.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Drawable gradient = AppCompatResources.getDrawable(
|
Drawable gradient = AppCompatResources.getDrawable(
|
||||||
|
@ -3,6 +3,8 @@ package org.session.libsession.avatars;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.makeramen.roundedimageview.RoundedDrawable;
|
import com.makeramen.roundedimageview.RoundedDrawable;
|
||||||
|
|
||||||
public class TransparentContactPhoto implements FallbackContactPhoto {
|
public class TransparentContactPhoto implements FallbackContactPhoto {
|
||||||
@ -10,13 +12,13 @@ public class TransparentContactPhoto implements FallbackContactPhoto {
|
|||||||
public TransparentContactPhoto() {}
|
public TransparentContactPhoto() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Drawable asDrawable(Context context, int color) {
|
public Drawable asDrawable(Context context, int color, boolean inverted) {
|
||||||
return asDrawable(context, color, false);
|
return asDrawable(context, color, inverted, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Drawable asDrawable(Context context, int color, boolean inverted) {
|
public Drawable asDrawable(Context context, int color, boolean inverted, Float padding) {
|
||||||
return RoundedDrawable.fromDrawable(context.getResources().getDrawable(android.R.color.transparent));
|
return RoundedDrawable.fromDrawable(ContextCompat.getDrawable(context, android.R.color.transparent));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -266,22 +266,6 @@
|
|||||||
<attr name="labeledEditText_textLayout" format="reference" />
|
<attr name="labeledEditText_textLayout" format="reference" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="WaveformSeekBar">
|
|
||||||
<attr name="progress" format="float"/>
|
|
||||||
<attr name="bar_width" format="dimension"/>
|
|
||||||
<attr name="bar_gap" format="dimension"/>
|
|
||||||
<attr name="bar_min_height" format="dimension"/>
|
|
||||||
<attr name="bar_corner_radius" format="dimension"/>
|
|
||||||
<attr name="bar_background_color" format="color"/>
|
|
||||||
<attr name="bar_progress_color" format="color"/>
|
|
||||||
<!-- Corresponds to WaveformSeekBar.WaveGravity enum. -->
|
|
||||||
<attr name="bar_gravity" format="enum">
|
|
||||||
<enum name="top" value="1" />
|
|
||||||
<enum name="center" value="2" />
|
|
||||||
<enum name="bottom" value="3" />
|
|
||||||
</attr>
|
|
||||||
</declare-styleable>
|
|
||||||
|
|
||||||
<declare-styleable name="KeyboardPageSearchView">
|
<declare-styleable name="KeyboardPageSearchView">
|
||||||
<attr name="show_always" format="boolean" />
|
<attr name="show_always" format="boolean" />
|
||||||
<attr name="search_bar_tint" format="color|reference" />
|
<attr name="search_bar_tint" format="color|reference" />
|
||||||
|
@ -6,3 +6,4 @@ include ':libsession'
|
|||||||
include ':libsignal'
|
include ':libsignal'
|
||||||
include ':libsession-util'
|
include ':libsession-util'
|
||||||
include ':content-descriptions' // ONLY AccessibilityID strings (non-translated) used to identify UI elements in automated testing
|
include ':content-descriptions' // ONLY AccessibilityID strings (non-translated) used to identify UI elements in automated testing
|
||||||
|
include ':stickyheader'
|
2
stickyheader/build.gradle
Normal file
2
stickyheader/build.gradle
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
configurations.maybeCreate("default")
|
||||||
|
artifacts.add("default", file('stickyheadergrid-0.9.4.aar'))
|
BIN
stickyheader/stickyheadergrid-0.9.4.aar
Normal file
BIN
stickyheader/stickyheadergrid-0.9.4.aar
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user