mirror of
https://github.com/deltachat/deltachat-android.git
synced 2025-10-03 09:49:21 +02:00
use new experimental maps integration (#3005)
* use new maps integration * Update src/com/b44t/messenger/DcContext.java Co-authored-by: bjoern <r10s@b44t.com> * Update src/org/thoughtcrime/securesms/WebxdcActivity.java Co-authored-by: bjoern <r10s@b44t.com> * fix typo --------- Co-authored-by: bjoern <r10s@b44t.com>
This commit is contained in:
parent
cb084df369
commit
9eaf8bb1f3
20 changed files with 68 additions and 2260 deletions
BIN
assets/webxdc/maps.xdc
Normal file
BIN
assets/webxdc/maps.xdc
Normal file
Binary file not shown.
|
@ -60,9 +60,6 @@ dependencies {
|
||||||
}
|
}
|
||||||
implementation 'com.annimon:stream:1.1.8' // brings future java streams api to SDK Version < 24
|
implementation 'com.annimon:stream:1.1.8' // brings future java streams api to SDK Version < 24
|
||||||
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' // glues the current time segment text in the gallery to the top.
|
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' // glues the current time segment text in the gallery to the top.
|
||||||
implementation ('org.maplibre.gl:android-sdk:9.5.2') {
|
|
||||||
exclude group: 'com.google.android.gms'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replacement for ContentResolver
|
// Replacement for ContentResolver
|
||||||
// that protects against the Surreptitious Sharing attack.
|
// that protects against the Surreptitious Sharing attack.
|
||||||
|
@ -99,7 +96,7 @@ android {
|
||||||
// Even though we compile our libraries outside Gradle with `scripts/ndk-make.sh`,
|
// Even though we compile our libraries outside Gradle with `scripts/ndk-make.sh`,
|
||||||
// without ndkVersion `./gradlew clean` followed by `./gradlew assembleDebug --warning-mode=all` emits the following warning:
|
// without ndkVersion `./gradlew clean` followed by `./gradlew assembleDebug --warning-mode=all` emits the following warning:
|
||||||
// > Task :stripFatDebugDebugSymbols
|
// > Task :stripFatDebugDebugSymbols
|
||||||
// Unable to strip the following libraries, packaging them as they are: libanimation-decoder-gif.so, libmapbox-gl.so, libnative-utils.so.
|
// Unable to strip the following libraries, packaging them as they are: libanimation-decoder-gif.so, libnative-utils.so.
|
||||||
// See <https://issuetracker.google.com/issues/237187538> for details.
|
// See <https://issuetracker.google.com/issues/237187538> for details.
|
||||||
ndkVersion "23.2.8568313"
|
ndkVersion "23.2.8568313"
|
||||||
useLibrary 'org.apache.http.legacy'
|
useLibrary 'org.apache.http.legacy'
|
||||||
|
@ -120,7 +117,6 @@ android {
|
||||||
project.ext.set("archivesBaseName", "deltachat");
|
project.ext.set("archivesBaseName", "deltachat");
|
||||||
|
|
||||||
buildConfigField "boolean", "DEV_BUILD", "false"
|
buildConfigField "boolean", "DEV_BUILD", "false"
|
||||||
buildConfigField "String", "MAP_ACCESS_TOKEN", '"pk.eyJ1IjoiZGVsdGFjaGF0IiwiYSI6ImNqc3c1aWczMzBjejY0M28wZmU0a3cwMzMifQ.ZPTH9dFJaav06RAu4rTYHw"'
|
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
||||||
|
|
|
@ -792,6 +792,20 @@ JNIEXPORT jstring Java_com_b44t_messenger_DcContext_getWebxdcStatusUpdates(JNIEn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JNIEXPORT jint Java_com_b44t_messenger_DcContext_initWebxdcIntegration(JNIEnv *env, jobject obj, jint chat_id)
|
||||||
|
{
|
||||||
|
return dc_init_webxdc_integration(get_dc_context(env, obj), chat_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JNIEXPORT void Java_com_b44t_messenger_DcContext_setWebxdcIntegration(JNIEnv *env, jobject obj, jstring file)
|
||||||
|
{
|
||||||
|
CHAR_REF(file);
|
||||||
|
dc_set_webxdc_integration(get_dc_context(env, obj), filePtr);
|
||||||
|
CHAR_UNREF(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
JNIEXPORT jint Java_com_b44t_messenger_DcContext_addDeviceMsg(JNIEnv *env, jobject obj, jstring label, jobject msg)
|
JNIEXPORT jint Java_com_b44t_messenger_DcContext_addDeviceMsg(JNIEnv *env, jobject obj, jstring label, jobject msg)
|
||||||
{
|
{
|
||||||
CHAR_REF(label);
|
CHAR_REF(label);
|
||||||
|
|
|
@ -202,6 +202,8 @@ public class DcContext {
|
||||||
public native int sendVideochatInvitation(int chat_id);
|
public native int sendVideochatInvitation(int chat_id);
|
||||||
public native boolean sendWebxdcStatusUpdate(int msg_id, String payload, String descr);
|
public native boolean sendWebxdcStatusUpdate(int msg_id, String payload, String descr);
|
||||||
public native String getWebxdcStatusUpdates(int msg_id, int last_known_serial);
|
public native String getWebxdcStatusUpdates(int msg_id, int last_known_serial);
|
||||||
|
public native void setWebxdcIntegration (String file);
|
||||||
|
public native int initWebxdcIntegration(int chat_id);
|
||||||
public native int addDeviceMsg (String label, DcMsg msg);
|
public native int addDeviceMsg (String label, DcMsg msg);
|
||||||
public native boolean wasDeviceMsgEverAdded(String label);
|
public native boolean wasDeviceMsgEverAdded(String label);
|
||||||
public DcLot checkQr (String qr) { return new DcLot(checkQrCPtr(qr)); }
|
public DcLot checkQr (String qr) { return new DcLot(checkQrCPtr(qr)); }
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||||
|
import static org.thoughtcrime.securesms.util.RelayUtil.getSharedText;
|
||||||
|
import static org.thoughtcrime.securesms.util.RelayUtil.isForwarding;
|
||||||
|
import static org.thoughtcrime.securesms.util.RelayUtil.isSharing;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
|
@ -50,7 +55,6 @@ import android.view.WindowManager;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
@ -93,7 +97,6 @@ import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.connect.DirectShareUtil;
|
import org.thoughtcrime.securesms.connect.DirectShareUtil;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.map.MapActivity;
|
|
||||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestsBottomView;
|
import org.thoughtcrime.securesms.messagerequests.MessageRequestsBottomView;
|
||||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||||
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
|
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
|
||||||
|
@ -131,11 +134,6 @@ import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
|
||||||
import static org.thoughtcrime.securesms.util.RelayUtil.getSharedText;
|
|
||||||
import static org.thoughtcrime.securesms.util.RelayUtil.isForwarding;
|
|
||||||
import static org.thoughtcrime.securesms.util.RelayUtil.isSharing;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for displaying a message thread, as well as
|
* Activity for displaying a message thread, as well as
|
||||||
* composing/sending a new message into that thread.
|
* composing/sending a new message into that thread.
|
||||||
|
@ -532,7 +530,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
case R.id.menu_clear_chat: fragment.handleClearChat(); return true;
|
case R.id.menu_clear_chat: fragment.handleClearChat(); return true;
|
||||||
case R.id.menu_delete_chat: handleDeleteChat(); return true;
|
case R.id.menu_delete_chat: handleDeleteChat(); return true;
|
||||||
case R.id.menu_mute_notifications: handleMuteNotifications(); return true;
|
case R.id.menu_mute_notifications: handleMuteNotifications(); return true;
|
||||||
case R.id.menu_show_map: handleShowMap(); return true;
|
case R.id.menu_show_map: WebxdcActivity.openMaps(this, chatId); return true;
|
||||||
case R.id.menu_search_up: handleMenuSearchNext(false); return true;
|
case R.id.menu_search_up: handleMenuSearchNext(false); return true;
|
||||||
case R.id.menu_search_down: handleMenuSearchNext(true); return true;
|
case R.id.menu_search_down: handleMenuSearchNext(true); return true;
|
||||||
case android.R.id.home: handleReturnToConversationList(); return true;
|
case android.R.id.home: handleReturnToConversationList(); return true;
|
||||||
|
@ -584,12 +582,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
new VideochatUtil().invite(this, chatId);
|
new VideochatUtil().invite(this, chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleShowMap() {
|
|
||||||
Intent intent = new Intent(this, MapActivity.class);
|
|
||||||
intent.putExtra(MapActivity.CHAT_ID, chatId);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleReturnToConversationList() {
|
private void handleReturnToConversationList() {
|
||||||
handleReturnToConversationList(null);
|
handleReturnToConversationList(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.ConversationActivity.CHAT_ID_EXTRA;
|
import static org.thoughtcrime.securesms.ConversationActivity.CHAT_ID_EXTRA;
|
||||||
import static org.thoughtcrime.securesms.ConversationActivity.STARTING_POSITION_EXTRA;
|
import static org.thoughtcrime.securesms.ConversationActivity.STARTING_POSITION_EXTRA;
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.ALL_CHATS_GLOBAL_MAP;
|
|
||||||
import static org.thoughtcrime.securesms.util.RelayUtil.acquireRelayMessageContent;
|
import static org.thoughtcrime.securesms.util.RelayUtil.acquireRelayMessageContent;
|
||||||
import static org.thoughtcrime.securesms.util.RelayUtil.getDirectSharingChatId;
|
import static org.thoughtcrime.securesms.util.RelayUtil.getDirectSharingChatId;
|
||||||
import static org.thoughtcrime.securesms.util.RelayUtil.getSharedTitle;
|
import static org.thoughtcrime.securesms.util.RelayUtil.getSharedTitle;
|
||||||
|
@ -60,7 +59,6 @@ import org.thoughtcrime.securesms.components.SearchToolbar;
|
||||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.connect.DirectShareUtil;
|
import org.thoughtcrime.securesms.connect.DirectShareUtil;
|
||||||
import org.thoughtcrime.securesms.map.MapActivity;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.qr.QrActivity;
|
import org.thoughtcrime.securesms.qr.QrActivity;
|
||||||
|
@ -74,9 +72,6 @@ import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
|
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
|
||||||
implements ConversationListFragment.ConversationSelectedListener
|
implements ConversationListFragment.ConversationSelectedListener
|
||||||
{
|
{
|
||||||
|
@ -384,7 +379,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||||
new IntentIntegrator(this).setCaptureActivity(QrActivity.class).initiateScan();
|
new IntentIntegrator(this).setCaptureActivity(QrActivity.class).initiateScan();
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_global_map:
|
case R.id.menu_global_map:
|
||||||
handleShowMap();
|
WebxdcActivity.openMaps(this, 0);
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_switch_account:
|
case R.id.menu_switch_account:
|
||||||
AccountManager.getInstance().showSwitchAccountMenu(this);
|
AccountManager.getInstance().showSwitchAccountMenu(this);
|
||||||
|
@ -423,13 +418,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleShowMap() {
|
|
||||||
Intent intent = new Intent(this, MapActivity.class);
|
|
||||||
intent.putExtra(MapActivity.CHAT_IDS, ALL_CHATS_GLOBAL_MAP);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateConversation(int chatId) {
|
public void onCreateConversation(int chatId) {
|
||||||
openConversation(chatId, -1);
|
openConversation(chatId, -1);
|
||||||
|
|
|
@ -48,6 +48,8 @@ import org.thoughtcrime.securesms.util.Prefs;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -55,33 +57,64 @@ import java.util.Map;
|
||||||
|
|
||||||
public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
|
public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
|
||||||
private static final String TAG = WebxdcActivity.class.getSimpleName();
|
private static final String TAG = WebxdcActivity.class.getSimpleName();
|
||||||
|
private static final String EXTRA_ACCOUNT_ID = "accountId";
|
||||||
|
private static final String EXTRA_APP_MSG_ID = "appMessageId";
|
||||||
|
private static final String EXTRA_HIDE_ACTION_BAR = "hideActionBar";
|
||||||
private static final int REQUEST_CODE_FILE_PICKER = 51426;
|
private static final int REQUEST_CODE_FILE_PICKER = 51426;
|
||||||
|
|
||||||
private ValueCallback<Uri[]> filePathCallback;
|
private ValueCallback<Uri[]> filePathCallback;
|
||||||
private DcContext dcContext;
|
private DcContext dcContext;
|
||||||
private DcMsg dcAppMsg;
|
private DcMsg dcAppMsg;
|
||||||
private String baseURL;
|
private String baseURL;
|
||||||
private String sourceCodeUrl = "";
|
private String sourceCodeUrl = "";
|
||||||
private boolean internetAccess = false;
|
private boolean internetAccess = false;
|
||||||
|
private boolean hideActionBar = false;
|
||||||
|
|
||||||
|
public static void openMaps(Context context, int chatId) {
|
||||||
|
DcContext dcContext = DcHelper.getContext(context);
|
||||||
|
int msgId = dcContext.initWebxdcIntegration(chatId);
|
||||||
|
if (msgId == 0) {
|
||||||
|
try {
|
||||||
|
InputStream inputStream = context.getResources().getAssets().open("webxdc/maps.xdc");
|
||||||
|
String outputFile = DcHelper.getBlobdirFile(dcContext, "maps", ".xdc");
|
||||||
|
Util.copy(inputStream, new FileOutputStream(outputFile));
|
||||||
|
dcContext.setWebxdcIntegration(outputFile);
|
||||||
|
msgId = dcContext.initWebxdcIntegration(chatId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (msgId == 0) {
|
||||||
|
Toast.makeText(context, "Cannot get maps.xdc, see log for details.", Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
openWebxdcActivity(context, msgId, true);
|
||||||
|
}
|
||||||
|
|
||||||
public static void openWebxdcActivity(Context context, DcMsg instance) {
|
public static void openWebxdcActivity(Context context, DcMsg instance) {
|
||||||
|
openWebxdcActivity(context, instance.getId(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openWebxdcActivity(Context context, int msgId, boolean hideActionBar) {
|
||||||
if (!Util.isClickedRecently()) {
|
if (!Util.isClickedRecently()) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
if (Prefs.isDeveloperModeEnabled(context)) {
|
if (Prefs.isDeveloperModeEnabled(context)) {
|
||||||
WebView.setWebContentsDebuggingEnabled(true);
|
WebView.setWebContentsDebuggingEnabled(true);
|
||||||
}
|
}
|
||||||
context.startActivity(getWebxdcIntent(context, instance.getId()));
|
context.startActivity(getWebxdcIntent(context, msgId, hideActionBar));
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(context, "At least Android 5.0 (Lollipop) required for Webxdc.", Toast.LENGTH_LONG).show();
|
Toast.makeText(context, "At least Android 5.0 (Lollipop) required for Webxdc.", Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Intent getWebxdcIntent(Context context, int msgId) {
|
private static Intent getWebxdcIntent(Context context, int msgId, boolean hideActionBar) {
|
||||||
DcContext dcContext = DcHelper.getContext(context);
|
DcContext dcContext = DcHelper.getContext(context);
|
||||||
Intent intent = new Intent(context, WebxdcActivity.class);
|
Intent intent = new Intent(context, WebxdcActivity.class);
|
||||||
intent.setAction(Intent.ACTION_VIEW);
|
intent.setAction(Intent.ACTION_VIEW);
|
||||||
intent.putExtra("accountId", dcContext.getAccountId());
|
intent.putExtra(EXTRA_ACCOUNT_ID, dcContext.getAccountId());
|
||||||
intent.putExtra("appMessageId", msgId);
|
intent.putExtra(EXTRA_APP_MSG_ID, msgId);
|
||||||
|
intent.putExtra(EXTRA_HIDE_ACTION_BAR, hideActionBar);
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +125,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||||
.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcContext.getMsg(msgId).getChatId())
|
.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcContext.getMsg(msgId).getChatId())
|
||||||
.setAction(Intent.ACTION_VIEW);
|
.setAction(Intent.ACTION_VIEW);
|
||||||
|
|
||||||
final Intent webxdcIntent = getWebxdcIntent(context, msgId);
|
final Intent webxdcIntent = getWebxdcIntent(context, msgId, false);
|
||||||
|
|
||||||
return TaskStackBuilder.create(context)
|
return TaskStackBuilder.create(context)
|
||||||
.addNextIntentWithParentStack(chatIntent)
|
.addNextIntentWithParentStack(chatIntent)
|
||||||
|
@ -104,6 +137,9 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||||
protected void onCreate(Bundle state, boolean ready) {
|
protected void onCreate(Bundle state, boolean ready) {
|
||||||
super.onCreate(state, ready);
|
super.onCreate(state, ready);
|
||||||
|
|
||||||
|
Bundle b = getIntent().getExtras();
|
||||||
|
hideActionBar = b.getBoolean(EXTRA_HIDE_ACTION_BAR, false);
|
||||||
|
|
||||||
// enter fullscreen mode if necessary,
|
// enter fullscreen mode if necessary,
|
||||||
// this is needed here because if the app is opened while already in landscape mode, onConfigurationChanged() is not triggered
|
// this is needed here because if the app is opened while already in landscape mode, onConfigurationChanged() is not triggered
|
||||||
setScreenMode(getResources().getConfiguration());
|
setScreenMode(getResources().getConfiguration());
|
||||||
|
@ -129,10 +165,8 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||||
eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
|
eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
|
||||||
eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
|
eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
|
||||||
|
|
||||||
Bundle b = getIntent().getExtras();
|
int appMessageId = b.getInt(EXTRA_APP_MSG_ID);
|
||||||
int appMessageId = b.getInt("appMessageId");
|
int accountId = b.getInt(EXTRA_ACCOUNT_ID);
|
||||||
|
|
||||||
int accountId = b.getInt("accountId");
|
|
||||||
this.dcContext = DcHelper.getContext(getApplicationContext());
|
this.dcContext = DcHelper.getContext(getApplicationContext());
|
||||||
if (accountId != dcContext.getAccountId()) {
|
if (accountId != dcContext.getAccountId()) {
|
||||||
AccountManager.getInstance().switchAccount(getApplicationContext(), accountId);
|
AccountManager.getInstance().switchAccount(getApplicationContext(), accountId);
|
||||||
|
@ -224,7 +258,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
|
||||||
getWindow().getDecorView().setSystemUiVisibility(enable? View.SYSTEM_UI_FLAG_FULLSCREEN : 0);
|
getWindow().getDecorView().setSystemUiVisibility(enable? View.SYSTEM_UI_FLAG_FULLSCREEN : 0);
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
if (actionBar != null) {
|
if (actionBar != null) {
|
||||||
if (enable) {
|
if (hideActionBar || enable) {
|
||||||
actionBar.hide();
|
actionBar.hide();
|
||||||
} else {
|
} else {
|
||||||
actionBar.show();
|
actionBar.show();
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.appcompat.widget.LinearLayoutCompat;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
|
|
||||||
import com.b44t.messenger.DcContext;
|
|
||||||
import com.b44t.messenger.DcMsg;
|
|
||||||
import com.mapbox.mapboxsdk.geometry.LatLng;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by cyberta on 24.04.19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class AddPoiView extends LinearLayoutCompat {
|
|
||||||
private final ImageButton sendView;
|
|
||||||
private final EditText messageView;
|
|
||||||
private final ProgressBar progressBar;
|
|
||||||
private LatLng latLng;
|
|
||||||
private SendingTask.OnMessageSentListener listener;
|
|
||||||
private int chatId;
|
|
||||||
|
|
||||||
public AddPoiView(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddPoiView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddPoiView(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
|
|
||||||
inflate(context, R.layout.add_poi_view, this);
|
|
||||||
sendView = this.findViewById(R.id.sendView);
|
|
||||||
messageView = this.findViewById(R.id.message_view);
|
|
||||||
progressBar = this.findViewById(R.id.sending_progress);
|
|
||||||
|
|
||||||
messageView.requestFocus();
|
|
||||||
sendView.setOnClickListener(v -> {
|
|
||||||
if (messageView.getText().toString().length() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
progressBar.setVisibility(VISIBLE);
|
|
||||||
sendView.setVisibility(INVISIBLE);
|
|
||||||
DcContext dcContext = DcHelper.getContext(AddPoiView.this.getContext());
|
|
||||||
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
|
|
||||||
msg.setLocation((float) latLng.getLatitude(), (float) latLng.getLongitude());
|
|
||||||
msg.setText(messageView.getText().toString());
|
|
||||||
|
|
||||||
SendingTask.Model model = new SendingTask.Model(msg, chatId, listener);
|
|
||||||
new SendingTask(AddPoiView.this.getContext()).execute(model);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLatLng(LatLng latLng) {
|
|
||||||
this.latLng = latLng;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChatId(int chatId) {
|
|
||||||
this.chatId = chatId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnMessageSentListener(SendingTask.OnMessageSentListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EditText getMessageView() {
|
|
||||||
return messageView;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,220 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import com.mapbox.mapboxsdk.maps.MapFragment;
|
|
||||||
import com.mapbox.mapboxsdk.maps.MapView;
|
|
||||||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
|
||||||
import com.mapbox.mapboxsdk.maps.MapboxMapOptions;
|
|
||||||
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
|
|
||||||
import com.mapbox.mapboxsdk.utils.MapFragmentUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Support Fragment wrapper around a map view.
|
|
||||||
* <p>
|
|
||||||
* A Map component in an app. This fragment is the simplest way to place a map in an application.
|
|
||||||
* It's a wrapper around a view of a map to automatically handle the necessary life cycle needs.
|
|
||||||
* Being a fragment, this component can be added to an activity's layout or can dynamically be added
|
|
||||||
* using a FragmentManager.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* To get a reference to the MapView, use {@link #getMapAsync(OnMapReadyCallback)}}
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @see #getMapAsync(OnMapReadyCallback)
|
|
||||||
*/
|
|
||||||
public class DCMapFragment extends Fragment implements OnMapReadyCallback {
|
|
||||||
|
|
||||||
private final List<OnMapReadyCallback> mapReadyCallbackList = new ArrayList<>();
|
|
||||||
private MapFragment.OnMapViewReadyCallback mapViewReadyCallback;
|
|
||||||
private MapboxMap mapboxMap;
|
|
||||||
private MapView map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a default MapFragment instance
|
|
||||||
*
|
|
||||||
* @return MapFragment created
|
|
||||||
*/
|
|
||||||
public static DCMapFragment newInstance() {
|
|
||||||
return new DCMapFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a MapFragment instance
|
|
||||||
*
|
|
||||||
* @param mapboxMapOptions The configuration options to be used.
|
|
||||||
* @return MapFragment created.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static DCMapFragment newInstance(@Nullable MapboxMapOptions mapboxMapOptions) {
|
|
||||||
DCMapFragment mapFragment = new DCMapFragment();
|
|
||||||
mapFragment.setArguments(MapFragmentUtils.createFragmentArgs(mapboxMapOptions));
|
|
||||||
return mapFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the context attaches to this fragment.
|
|
||||||
*
|
|
||||||
* @param context the context attaching
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
if (context instanceof MapFragment.OnMapViewReadyCallback) {
|
|
||||||
mapViewReadyCallback = (MapFragment.OnMapViewReadyCallback) context;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when this fragment is inflated, parses XML tag attributes.
|
|
||||||
*
|
|
||||||
* @param context The context inflating this fragment.
|
|
||||||
* @param attrs The XML tag attributes.
|
|
||||||
* @param savedInstanceState The saved instance state for the map fragment.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onInflate(@NonNull Context context, AttributeSet attrs, Bundle savedInstanceState) {
|
|
||||||
super.onInflate(context, attrs, savedInstanceState);
|
|
||||||
setArguments(MapFragmentUtils.createFragmentArgs(MapboxMapOptions.createFromAttributes(context, attrs)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the fragment view hierarchy.
|
|
||||||
*
|
|
||||||
* @param inflater Inflater used to inflate content.
|
|
||||||
* @param container The parent layout for the map fragment.
|
|
||||||
* @param savedInstanceState The saved instance state for the map fragment.
|
|
||||||
* @return The view created
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
Context context = inflater.getContext();
|
|
||||||
map = new MapView(context, MapFragmentUtils.resolveArgs(context, getArguments()));
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the fragment view hierarchy is created.
|
|
||||||
*
|
|
||||||
* @param view The content view of the fragment
|
|
||||||
* @param savedInstanceState THe saved instance state of the framgnt
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
map.onCreate(savedInstanceState);
|
|
||||||
map.getMapAsync(this);
|
|
||||||
|
|
||||||
// notify listeners about MapView creation
|
|
||||||
if (mapViewReadyCallback != null) {
|
|
||||||
mapViewReadyCallback.onMapViewReady(map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMapReady(@NonNull MapboxMap mapboxMap) {
|
|
||||||
this.mapboxMap = mapboxMap;
|
|
||||||
for (OnMapReadyCallback onMapReadyCallback : mapReadyCallbackList) {
|
|
||||||
onMapReadyCallback.onMapReady(mapboxMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the fragment is visible for the users.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
map.onStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the fragment is ready to be interacted with.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
map.onResume();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the fragment is pausing.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
map.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the fragment state needs to be saved.
|
|
||||||
*
|
|
||||||
* @param outState The saved state
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
if (map != null && !map.isDestroyed()) {
|
|
||||||
map.onSaveInstanceState(outState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the fragment is no longer visible for the user.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
map.onStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the fragment receives onLowMemory call from the hosting Activity.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onLowMemory() {
|
|
||||||
super.onLowMemory();
|
|
||||||
if (map != null && !map.isDestroyed()) {
|
|
||||||
map.onLowMemory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the fragment is view hierarchy is being destroyed.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
map.onDestroy();
|
|
||||||
mapReadyCallbackList.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a callback object which will be triggered when the MapboxMap instance is ready to be used.
|
|
||||||
*
|
|
||||||
* @param onMapReadyCallback The callback to be invoked.
|
|
||||||
*/
|
|
||||||
public void getMapAsync(@NonNull final OnMapReadyCallback onMapReadyCallback) {
|
|
||||||
if (mapboxMap == null) {
|
|
||||||
mapReadyCallbackList.add(onMapReadyCallback);
|
|
||||||
} else {
|
|
||||||
onMapReadyCallback.onMapReady(mapboxMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MapView getMapView() {
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.b44t.messenger.DcContext;
|
|
||||||
import com.mapbox.geojson.Feature;
|
|
||||||
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
|
|
||||||
import org.thoughtcrime.securesms.map.model.MapSource;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.TIMESTAMP_NOW;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.TIME_FRAME;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by cyberta on 15.04.19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class DataCollectionTask extends AsyncTask<Void, Void, Set<String>> {
|
|
||||||
|
|
||||||
public interface DataCollectionCallback {
|
|
||||||
void onDataCollectionFinished(Set<String> emojiCodepoints);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String TAG = DataCollectionTask.class.getSimpleName();
|
|
||||||
private static final Set<DataCollectionTask> instances = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
|
||||||
|
|
||||||
private final int chatId;
|
|
||||||
private final int[] contactIds;
|
|
||||||
private final ConcurrentHashMap<Integer, MapSource> contactMapSources;
|
|
||||||
private final ConcurrentHashMap<String, LinkedList<Feature>> featureCollections;
|
|
||||||
private final ConcurrentHashMap<Integer, Feature> lastPositions;
|
|
||||||
private final LatLngBounds.Builder boundingBuilder;
|
|
||||||
private final DcContext dcContext;
|
|
||||||
private final DataCollectionCallback callback;
|
|
||||||
private final EmojiProvider emojiProvider;
|
|
||||||
|
|
||||||
public DataCollectionTask(DcContext context,
|
|
||||||
int chatId,
|
|
||||||
int[] contactIds,
|
|
||||||
ConcurrentHashMap<Integer, MapSource> contactMapSources,
|
|
||||||
ConcurrentHashMap featureCollections,
|
|
||||||
ConcurrentHashMap<Integer, Feature> lastPositions,
|
|
||||||
LatLngBounds.Builder boundingBuilder,
|
|
||||||
EmojiProvider emojiProvider,
|
|
||||||
DataCollectionCallback callback) {
|
|
||||||
this.chatId = chatId;
|
|
||||||
this.contactMapSources = contactMapSources;
|
|
||||||
this.featureCollections = featureCollections;
|
|
||||||
this.lastPositions = lastPositions;
|
|
||||||
this.boundingBuilder = boundingBuilder;
|
|
||||||
this.dcContext = context;
|
|
||||||
this.callback = callback;
|
|
||||||
this.contactIds = contactIds;
|
|
||||||
this.emojiProvider = emojiProvider;
|
|
||||||
instances.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void cancelRunningTasks() {
|
|
||||||
for (DataCollectionTask task : instances) {
|
|
||||||
task.cancel(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Set<String> doInBackground(Void... voids) {
|
|
||||||
Log.d(TAG, "performance test - collect Data start");
|
|
||||||
HashSet<String> emojiCodePoints = new HashSet<>();
|
|
||||||
DataCollector dataCollector = new DataCollector(dcContext,
|
|
||||||
contactMapSources,
|
|
||||||
featureCollections,
|
|
||||||
lastPositions,
|
|
||||||
emojiCodePoints,
|
|
||||||
emojiProvider,
|
|
||||||
boundingBuilder);
|
|
||||||
for (int contactId : contactIds) {
|
|
||||||
dataCollector.updateSource(chatId,
|
|
||||||
contactId,
|
|
||||||
System.currentTimeMillis() - TIME_FRAME,
|
|
||||||
TIMESTAMP_NOW);
|
|
||||||
if (this.isCancelled()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return emojiCodePoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Set<String> emojiCodePoints) {
|
|
||||||
if (!this.isCancelled()) {
|
|
||||||
callback.onDataCollectionFinished(emojiCodePoints);
|
|
||||||
}
|
|
||||||
instances.remove(this);
|
|
||||||
Log.d(TAG, "performance test - collect Data finished");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCancelled() {
|
|
||||||
super.onCancelled();
|
|
||||||
instances.remove(this);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,171 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map;
|
|
||||||
|
|
||||||
import com.b44t.messenger.DcArray;
|
|
||||||
import com.b44t.messenger.DcContact;
|
|
||||||
import com.b44t.messenger.DcContext;
|
|
||||||
import com.b44t.messenger.DcMsg;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.mapbox.geojson.Feature;
|
|
||||||
import com.mapbox.geojson.LineString;
|
|
||||||
import com.mapbox.geojson.Point;
|
|
||||||
import com.mapbox.mapboxsdk.geometry.LatLng;
|
|
||||||
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
|
|
||||||
import org.thoughtcrime.securesms.map.model.MapSource;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.ACCURACY;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.CONTACT_ID;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.IS_EMOJI_CHAR;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.IS_POI;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.LAST_LOCATION;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.LAST_POSITION_ICON;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.LAST_POSITION_LABEL;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.MARKER_CHAR;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.MARKER_ICON;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.MARKER_SELECTED;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.MESSAGE_ID;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.POI_LONG_DESCRIPTION;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.TIMESTAMP;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by cyberta on 18.04.19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class DataCollector {
|
|
||||||
|
|
||||||
|
|
||||||
private final DcContext dcContext;
|
|
||||||
private final ConcurrentHashMap<Integer, MapSource> contactMapSources;
|
|
||||||
private final ConcurrentHashMap<String, LinkedList<Feature>> featureCollections;
|
|
||||||
private final ConcurrentHashMap<Integer, Feature> lastPositions;
|
|
||||||
private final Set<String> emojiCodePoints;
|
|
||||||
private final LatLngBounds.Builder boundingBuilder;
|
|
||||||
private final EmojiProvider emojiProvider;
|
|
||||||
|
|
||||||
public DataCollector(DcContext dcContext,
|
|
||||||
ConcurrentHashMap<Integer, MapSource> contactMapSources,
|
|
||||||
ConcurrentHashMap<String, LinkedList<Feature>> featureCollections,
|
|
||||||
ConcurrentHashMap<Integer, Feature> lastPositions,
|
|
||||||
Set<String> emojiCodePoints,
|
|
||||||
EmojiProvider emojiProvider,
|
|
||||||
LatLngBounds.Builder boundingBuilder) {
|
|
||||||
this.dcContext = dcContext;
|
|
||||||
this.contactMapSources = contactMapSources;
|
|
||||||
this.featureCollections = featureCollections;
|
|
||||||
this.lastPositions = lastPositions;
|
|
||||||
this.boundingBuilder = boundingBuilder;
|
|
||||||
this.emojiCodePoints = emojiCodePoints;
|
|
||||||
this.emojiProvider = emojiProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void updateSource(int chatId,
|
|
||||||
int contactId,
|
|
||||||
long startTimestamp,
|
|
||||||
long endTimestamp) {
|
|
||||||
DcArray locations = dcContext.getLocations(chatId, contactId, startTimestamp, endTimestamp);
|
|
||||||
|
|
||||||
MapSource contactMapMetadata = contactMapSources.get(contactId);
|
|
||||||
if (contactMapMetadata == null) {
|
|
||||||
contactMapMetadata = addContactMapSource(contactMapSources, contactId);
|
|
||||||
}
|
|
||||||
int count = locations.getCnt();
|
|
||||||
|
|
||||||
LinkedList<Feature> sortedPointFeatures = featureCollections.get(contactMapMetadata.getMarkerFeatureCollection());
|
|
||||||
if (sortedPointFeatures != null && sortedPointFeatures.size() == count) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
sortedPointFeatures = new LinkedList<>();
|
|
||||||
}
|
|
||||||
LinkedList<Feature> sortedLineFeatures = new LinkedList<>();
|
|
||||||
|
|
||||||
for (int i = count - 1; i >= 0; i--) {
|
|
||||||
Point point = Point.fromLngLat(locations.getLongitude(i), locations.getLatitude(i));
|
|
||||||
|
|
||||||
String codepointChar =
|
|
||||||
locations.getMarker(i) != null ?
|
|
||||||
locations.getMarker(i) :
|
|
||||||
"";
|
|
||||||
boolean isPoi = locations.isIndependent(i);
|
|
||||||
int messageId = locations.getMsgId(i);
|
|
||||||
boolean isEmojiChar = !codepointChar.isEmpty() && emojiProvider.isEmoji(codepointChar);
|
|
||||||
|
|
||||||
Feature pointFeature = Feature.fromGeometry(point, new JsonObject(), String.valueOf(locations.getLocationId(i)));
|
|
||||||
pointFeature.addBooleanProperty(MARKER_SELECTED, false);
|
|
||||||
pointFeature.addBooleanProperty(LAST_LOCATION, false);
|
|
||||||
pointFeature.addNumberProperty(CONTACT_ID, contactId);
|
|
||||||
pointFeature.addNumberProperty(TIMESTAMP, locations.getTimestamp(i));
|
|
||||||
pointFeature.addNumberProperty(MESSAGE_ID, messageId);
|
|
||||||
pointFeature.addNumberProperty(ACCURACY, locations.getAccuracy(i));
|
|
||||||
pointFeature.addStringProperty(MARKER_CHAR, codepointChar);
|
|
||||||
pointFeature.addBooleanProperty(IS_EMOJI_CHAR, isEmojiChar);
|
|
||||||
if (isPoi && isEmojiChar) {
|
|
||||||
//we save emoji bitmaps in mapboxstyle with the codepoint as the key
|
|
||||||
pointFeature.addStringProperty(MARKER_ICON, codepointChar);
|
|
||||||
emojiCodePoints.add(codepointChar);
|
|
||||||
} else if (isPoi) {
|
|
||||||
pointFeature.addStringProperty(MARKER_ICON, contactMapMetadata.getMarkerPoi());
|
|
||||||
} else {
|
|
||||||
pointFeature.addStringProperty(MARKER_ICON, contactMapMetadata.getMarkerIcon());
|
|
||||||
}
|
|
||||||
|
|
||||||
pointFeature.addBooleanProperty(IS_POI, isPoi);
|
|
||||||
if (isPoi && codepointChar.length() == 0 && messageId != 0) {
|
|
||||||
//has a long poi label
|
|
||||||
DcMsg poiMsg = dcContext.getMsg(messageId);
|
|
||||||
String poiLongDescription = poiMsg.getSummarytext(16);
|
|
||||||
pointFeature.addStringProperty(POI_LONG_DESCRIPTION, poiLongDescription);
|
|
||||||
}
|
|
||||||
|
|
||||||
sortedPointFeatures.addFirst(pointFeature);
|
|
||||||
|
|
||||||
if (!locations.isIndependent(i) && sortedPointFeatures.size() > 1) {
|
|
||||||
Point lastPoint = (Point) sortedPointFeatures.get(1).geometry();
|
|
||||||
ArrayList<Point> lineSegmentPoints = new ArrayList<>(3);
|
|
||||||
lineSegmentPoints.add(lastPoint);
|
|
||||||
lineSegmentPoints.add(point);
|
|
||||||
LineString l = LineString.fromLngLats(lineSegmentPoints);
|
|
||||||
Feature lineFeature = Feature.fromGeometry(l, new JsonObject(), "l_" + pointFeature.id());
|
|
||||||
lineFeature.addNumberProperty(TIMESTAMP, pointFeature.getNumberProperty(TIMESTAMP));
|
|
||||||
sortedLineFeatures.addFirst(lineFeature);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boundingBuilder != null) {
|
|
||||||
boundingBuilder.include(new LatLng(locations.getLatitude(i), locations.getLongitude(i)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortedPointFeatures.size() > 0) {
|
|
||||||
for (Feature position : sortedPointFeatures) {
|
|
||||||
if (!position.getBooleanProperty(IS_POI)) {
|
|
||||||
position.addStringProperty(LAST_POSITION_ICON, contactMapMetadata.getMarkerLastPositon());
|
|
||||||
position.addStringProperty(LAST_POSITION_LABEL, contactMapMetadata.getDisplayName());
|
|
||||||
position.removeProperty(MARKER_ICON);
|
|
||||||
position.addBooleanProperty(LAST_LOCATION, true);
|
|
||||||
lastPositions.put(contactId, position);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
featureCollections.put(contactMapMetadata.getMarkerFeatureCollection(), sortedPointFeatures);
|
|
||||||
featureCollections.put(contactMapMetadata.getLineFeatureCollection(), sortedLineFeatures);
|
|
||||||
}
|
|
||||||
|
|
||||||
private MapSource addContactMapSource(ConcurrentHashMap<Integer, MapSource> contactMapSources, int contactId) {
|
|
||||||
if (contactMapSources.get(contactId) != null) {
|
|
||||||
return contactMapSources.get(contactId);
|
|
||||||
}
|
|
||||||
|
|
||||||
DcContact contact = dcContext.getContact(contactId);
|
|
||||||
MapSource contactMapSource = new MapSource(contact);
|
|
||||||
contactMapSources.put(contactId, contactMapSource);
|
|
||||||
return contactMapSource;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,163 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by cyberta on 13.03.19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.b44t.messenger.DcContact;
|
|
||||||
import com.b44t.messenger.DcContext;
|
|
||||||
import com.b44t.messenger.DcMsg;
|
|
||||||
import com.mapbox.geojson.Feature;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
import org.thoughtcrime.securesms.components.ConversationItemFooter;
|
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.CONTACT_ID;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.MESSAGE_ID;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.TIMESTAMP;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AsyncTask to generate Bitmap from Views to be used as iconImage in a SymbolLayer.
|
|
||||||
* <p>
|
|
||||||
* Call be optionally be called to update the underlying data source after execution.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* Generating Views on background thread since we are not going to be adding them to the view hierarchy.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
class GenerateInfoWindowTask extends AsyncTask<Feature, Bitmap, Bitmap> {
|
|
||||||
|
|
||||||
public interface GenerateInfoWindowCallback {
|
|
||||||
Context getContext();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the bitmaps have been generated from a view.
|
|
||||||
*/
|
|
||||||
void setInfoWindowResults(Bitmap result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String TAG = GenerateInfoWindowCallback.class.getName();
|
|
||||||
private final WeakReference<GenerateInfoWindowCallback> callbackRef;
|
|
||||||
private final static HashSet<GenerateInfoWindowTask> instances = new HashSet<>();
|
|
||||||
|
|
||||||
GenerateInfoWindowTask(GenerateInfoWindowCallback callback) {
|
|
||||||
this.callbackRef = new WeakReference<>(callback);
|
|
||||||
instances.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void cancelRunningTasks() {
|
|
||||||
for (GenerateInfoWindowTask task : instances) {
|
|
||||||
task.cancel(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("WrongThread")
|
|
||||||
@Override
|
|
||||||
protected Bitmap doInBackground(Feature... params) {
|
|
||||||
Log.d(TAG, "GenerateInfoWindowTask start");
|
|
||||||
Thread.currentThread().setName(GenerateInfoWindowTask.class.getName());
|
|
||||||
Bitmap bitmap = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(callbackRef.get().getContext());
|
|
||||||
|
|
||||||
Feature feature = params[0];
|
|
||||||
Log.d(TAG, "GenerateInfoWindowTask: feature " + feature.id());
|
|
||||||
|
|
||||||
LinearLayout bubbleLayout = (LinearLayout)
|
|
||||||
inflater.inflate(R.layout.map_bubble_layout, null);
|
|
||||||
bubbleLayout.setBackgroundResource(R.drawable.message_bubble_background_received_alone);
|
|
||||||
EmojiTextView conversationItemBody = bubbleLayout.findViewById(R.id.conversation_item_body);
|
|
||||||
Locale locale = DynamicLanguage.getSelectedLocale(callbackRef.get().getContext());
|
|
||||||
int messageId = (int) feature.getNumberProperty(MESSAGE_ID);
|
|
||||||
int contactId = (int) feature.getNumberProperty(CONTACT_ID);
|
|
||||||
|
|
||||||
DcContact contact = DcHelper.getContext(callbackRef.get().getContext()).getContact(contactId);
|
|
||||||
TextView contactTextView = bubbleLayout.findViewById(R.id.message_sender);
|
|
||||||
contactTextView.setText(contact.getDisplayName());
|
|
||||||
|
|
||||||
String msgText;
|
|
||||||
if (messageId != 0) {
|
|
||||||
DcContext dcContext = DcHelper.getContext(callbackRef.get().getContext());
|
|
||||||
DcMsg msg = dcContext.getMsg(messageId);
|
|
||||||
if (hasImgThumbnail(msg)) {
|
|
||||||
ImageView thumbnailView = bubbleLayout.findViewById(R.id.map_bubble_img_thumbnail);
|
|
||||||
thumbnailView.setImageURI(getThumbnailUri(msg));
|
|
||||||
thumbnailView.setVisibility(View.VISIBLE);
|
|
||||||
msgText = msg.getText();
|
|
||||||
} else {
|
|
||||||
msgText = msg.getSummarytext(75);
|
|
||||||
}
|
|
||||||
ConversationItemFooter footer = bubbleLayout.findViewById(R.id.conversation_item_footer);
|
|
||||||
footer.setVisibility(View.VISIBLE);
|
|
||||||
footer.setMessageRecord(msg, locale);
|
|
||||||
} else {
|
|
||||||
msgText = "Reported: " + DateUtils.getExtendedRelativeTimeSpanString(callbackRef.get().getContext(), locale, (long) feature.getNumberProperty(TIMESTAMP));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msgText.length() == 0) {
|
|
||||||
conversationItemBody.setVisibility(GONE);
|
|
||||||
} else {
|
|
||||||
conversationItemBody.setText(msgText);
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap = BitmapUtil.generate(bubbleLayout);
|
|
||||||
} catch (NullPointerException npe) {
|
|
||||||
npe.printStackTrace();
|
|
||||||
Log.e(TAG, "Callback was GC'ed before task finished.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "GenerateInfoWindowTask finished");
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Bitmap bitmap) {
|
|
||||||
if (!isCancelled() && bitmap != null) {
|
|
||||||
try {
|
|
||||||
callbackRef.get().setInfoWindowResults(bitmap);
|
|
||||||
} catch (NullPointerException npe) {
|
|
||||||
npe.printStackTrace();
|
|
||||||
Log.e(TAG, "Callback was GC'ed before task finished.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instances.remove(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCancelled() {
|
|
||||||
super.onCancelled();
|
|
||||||
instances.remove(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasImgThumbnail(DcMsg dcMsg) {
|
|
||||||
int type = dcMsg.getType();
|
|
||||||
return type == DcMsg.DC_MSG_IMAGE && dcMsg.hasFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Uri getThumbnailUri(DcMsg dcMsg) {
|
|
||||||
return Uri.fromFile(new File(dcMsg.getFile()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,370 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map;
|
|
||||||
|
|
||||||
import static com.b44t.messenger.DcChat.DC_CHAT_NO_CHAT;
|
|
||||||
import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
|
|
||||||
import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
|
|
||||||
import static com.mapbox.mapboxsdk.constants.MapboxConstants.MINIMUM_ZOOM;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.CONTACT_ID;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.IS_POI;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.MARKER_SELECTED;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.MESSAGE_ID;
|
|
||||||
import static org.thoughtcrime.securesms.map.model.MapSource.INFO_WINDOW_LAYER;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.PointF;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.widget.SwitchCompat;
|
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
|
|
||||||
import com.b44t.messenger.DcMsg;
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
|
||||||
import com.mapbox.geojson.Feature;
|
|
||||||
import com.mapbox.mapboxsdk.Mapbox;
|
|
||||||
import com.mapbox.mapboxsdk.WellKnownTileServer;
|
|
||||||
import com.mapbox.mapboxsdk.camera.CameraPosition;
|
|
||||||
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
|
|
||||||
import com.mapbox.mapboxsdk.geometry.LatLng;
|
|
||||||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
|
||||||
import com.mapbox.mapboxsdk.maps.Style;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.BaseActivity;
|
|
||||||
import org.thoughtcrime.securesms.BuildConfig;
|
|
||||||
import org.thoughtcrime.securesms.ConversationActivity;
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
import org.thoughtcrime.securesms.components.InputAwareLayout;
|
|
||||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
|
|
||||||
import org.thoughtcrime.securesms.components.rangeslider.TimeRangeSlider;
|
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
|
||||||
import org.thoughtcrime.securesms.geolocation.DcLocation;
|
|
||||||
import org.thoughtcrime.securesms.util.Prefs;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Observable;
|
|
||||||
import java.util.Observer;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
public class MapActivity extends BaseActivity implements Observer,
|
|
||||||
TimeRangeSlider.OnTimestampChangedListener,
|
|
||||||
KeyboardAwareLinearLayout.OnKeyboardShownListener,
|
|
||||||
KeyboardAwareLinearLayout.OnKeyboardHiddenListener {
|
|
||||||
|
|
||||||
public static final String TAG = MapActivity.class.getSimpleName();
|
|
||||||
public static final String CHAT_ID = "chat_id";
|
|
||||||
public static final String CHAT_IDS = "chat_id";
|
|
||||||
public static final String MAP_TAG = "org.thoughtcrime.securesms.map";
|
|
||||||
|
|
||||||
private DcLocation dcLocation;
|
|
||||||
private MapDataManager mapDataManager;
|
|
||||||
private MapboxMap mapboxMap;
|
|
||||||
DCMapFragment mapFragment;
|
|
||||||
MarkerViewManager markerViewManager;
|
|
||||||
private int chatId;
|
|
||||||
private InputAwareLayout inputAwareContainer;
|
|
||||||
|
|
||||||
public static void lazyMapboxInit(Context context) {
|
|
||||||
try {
|
|
||||||
Mapbox.getInstance(context, BuildConfig.MAP_ACCESS_TOKEN, WellKnownTileServer.Mapbox);
|
|
||||||
|
|
||||||
// disable telemetry. these functions are currently partly redundant,
|
|
||||||
// however, implementations may change
|
|
||||||
// and the two explicit calls seems to have worked in the past,
|
|
||||||
// see https://github.com/mapbox/mapbox-gl-native/issues/13304
|
|
||||||
// We are now using MapLibre, so there is no telemetry
|
|
||||||
// TelemetryEnabler.updateTelemetryState(TelemetryEnabler.State.DISABLED);
|
|
||||||
// TelemetryDefinition telemetry = Mapbox.getTelemetry();
|
|
||||||
// if (telemetry != null) {
|
|
||||||
// telemetry.setUserTelemetryRequestState(false);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
catch(Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
lazyMapboxInit(this);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_map);
|
|
||||||
chatId = getIntent().getIntExtra(CHAT_ID, -1);
|
|
||||||
|
|
||||||
if (chatId == -1) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dcLocation = DcLocation.getInstance();
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
|
||||||
mapFragment = DCMapFragment.newInstance();
|
|
||||||
transaction.add(R.id.container, mapFragment, MAP_TAG);
|
|
||||||
transaction.commit();
|
|
||||||
} else {
|
|
||||||
mapFragment = (DCMapFragment) getSupportFragmentManager().findFragmentByTag(MAP_TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
mapFragment.getMapAsync(mapboxMap -> mapboxMap.setStyle(Style.getPredefinedStyle("Streets"), style -> {
|
|
||||||
|
|
||||||
this.mapboxMap = mapboxMap;
|
|
||||||
this.markerViewManager = new MarkerViewManager(mapFragment.getMapView(), mapboxMap);
|
|
||||||
inputAwareContainer = findViewById(R.id.inputAwareContainer);
|
|
||||||
inputAwareContainer.addOnKeyboardHiddenListener(this);
|
|
||||||
inputAwareContainer.addOnKeyboardShownListener(this);
|
|
||||||
|
|
||||||
final int accountId = DcHelper.getContext(this.getApplicationContext()).getAccountId();
|
|
||||||
final LatLng lastMapCenter = Prefs.getMapCenter(this.getApplicationContext(), accountId, chatId);
|
|
||||||
if (lastMapCenter != null) {
|
|
||||||
double lastZoom = Prefs.getMapZoom(this.getApplicationContext(), accountId, chatId);
|
|
||||||
mapboxMap.setCameraPosition(new CameraPosition.Builder()
|
|
||||||
.target(lastMapCenter)
|
|
||||||
.zoom(lastZoom)
|
|
||||||
.build());
|
|
||||||
} else if (dcLocation.getLastLocation().getProvider().equals("?")) {
|
|
||||||
double randomLongitude = getRandomLongitude();
|
|
||||||
mapboxMap.setCameraPosition(new CameraPosition.Builder()
|
|
||||||
.target(new LatLng(0d, randomLongitude))
|
|
||||||
.zoom(MINIMUM_ZOOM)
|
|
||||||
.build());
|
|
||||||
} else {
|
|
||||||
mapboxMap.setCameraPosition(new CameraPosition.Builder()
|
|
||||||
.target(new LatLng(dcLocation.getLastLocation().getLatitude(), dcLocation.getLastLocation().getLongitude()))
|
|
||||||
.zoom(9)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
mapboxMap.getUiSettings().setLogoEnabled(false);
|
|
||||||
mapboxMap.getUiSettings().setAttributionEnabled(false);
|
|
||||||
|
|
||||||
Style mapBoxStyle = mapboxMap.getStyle();
|
|
||||||
if (mapBoxStyle == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mapDataManager = new MapDataManager(this,
|
|
||||||
mapBoxStyle,
|
|
||||||
mapboxMap.getLocationComponent(),
|
|
||||||
chatId,
|
|
||||||
(latLngBounds) -> {
|
|
||||||
Log.d(TAG, "on Data initialized");
|
|
||||||
if (latLngBounds != null && lastMapCenter == null) {
|
|
||||||
mapboxMap.easeCamera(CameraUpdateFactory.newLatLngBounds(latLngBounds, 50), 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
mapboxMap.addOnMapClickListener(point ->
|
|
||||||
handleInfoWindowClick(point) ||
|
|
||||||
handleMarkerClick(point) ||
|
|
||||||
handleAddPoiClick(point));
|
|
||||||
|
|
||||||
|
|
||||||
mapboxMap.addOnMapLongClickListener(this::handlePoiLongClick);
|
|
||||||
|
|
||||||
SwitchCompat switchCompat = this.findViewById(R.id.locationTraceSwitch);
|
|
||||||
switchCompat.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
|
||||||
mapDataManager.showTraces(isChecked);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
TimeRangeSlider timeRangeSlider = this.findViewById(R.id.timeRangeSlider);
|
|
||||||
timeRangeSlider.setOnTimestampChangedListener(this);
|
|
||||||
|
|
||||||
}));
|
|
||||||
|
|
||||||
View bottomSheet = this.findViewById(R.id.bottom_sheet);
|
|
||||||
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
|
|
||||||
|
|
||||||
RelativeLayout bottomSheetSlider = this.findViewById(R.id.bottomSheetSlider);
|
|
||||||
bottomSheetSlider.setOnClickListener(v -> {
|
|
||||||
switch (behavior.getState()) {
|
|
||||||
case STATE_EXPANDED:
|
|
||||||
behavior.setState(STATE_COLLAPSED);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
behavior.setState(STATE_EXPANDED);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
DcLocation.getInstance().addObserver(this);
|
|
||||||
if (mapDataManager != null) {
|
|
||||||
mapDataManager.onResume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
DcLocation.getInstance().deleteObserver(this);
|
|
||||||
if (mapDataManager != null) {
|
|
||||||
mapDataManager.onPause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
if (mapDataManager != null) {
|
|
||||||
Prefs.setMapCenter(this.getApplicationContext(), mapDataManager.getAccountId(), mapDataManager.getChatId(), mapboxMap.getCameraPosition().target);
|
|
||||||
Prefs.setMapZoom(this.getApplicationContext(), mapDataManager.getAccountId(), mapDataManager.getChatId(), mapboxMap.getCameraPosition().zoom);
|
|
||||||
mapDataManager.onDestroy();
|
|
||||||
}
|
|
||||||
if (markerViewManager != null) {
|
|
||||||
markerViewManager.onDestroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(Observable o, Object arg) {
|
|
||||||
if (o instanceof DcLocation) {
|
|
||||||
this.dcLocation = (DcLocation) o;
|
|
||||||
Log.d(TAG, "show marker on map: " +
|
|
||||||
dcLocation.getLastLocation().getLatitude() + ", " +
|
|
||||||
dcLocation.getLastLocation().getLongitude());
|
|
||||||
//TODO: consider implementing a button -> center map to current location
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTimestampChanged(long startTimestamp, long stopTimestamp) {
|
|
||||||
if (this.mapboxMap == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mapDataManager.filterRange(startTimestamp, stopTimestamp);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFilterLastPosition(long startTimestamp) {
|
|
||||||
if (this.mapboxMap == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mapDataManager.filterLastPositions(startTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double getRandomLongitude() {
|
|
||||||
double start = -180;
|
|
||||||
double end = 180;
|
|
||||||
double random = new Random().nextDouble();
|
|
||||||
return start + (random * (end - start));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean handleMarkerClick(LatLng point) {
|
|
||||||
final PointF pixel = mapboxMap.getProjection().toScreenLocation(point);
|
|
||||||
Log.d(TAG, "on item clicked.");
|
|
||||||
|
|
||||||
List<Feature> features = mapboxMap.queryRenderedFeatures(pixel, mapDataManager.getMarkerLayers());
|
|
||||||
for (Feature feature : features) {
|
|
||||||
Log.d(TAG, "found feature: " + feature.toJson());
|
|
||||||
//show first feature that has meta data infos
|
|
||||||
if (feature.hasProperty(MARKER_SELECTED)) {
|
|
||||||
mapDataManager.setMarkerSelected(feature.id());
|
|
||||||
if (markerViewManager.hasMarkers()) {
|
|
||||||
markerViewManager.removeMarkers();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mapDataManager.unselectMarker();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean handleInfoWindowClick(LatLng point) {
|
|
||||||
final PointF pixel = mapboxMap.getProjection().toScreenLocation(point);
|
|
||||||
|
|
||||||
List<Feature> features = mapboxMap.queryRenderedFeatures(pixel, INFO_WINDOW_LAYER);
|
|
||||||
Log.d(TAG, "on info window clicked." + features.size());
|
|
||||||
|
|
||||||
for (Feature feature : features) {
|
|
||||||
Log.d(TAG, "found feature: " + feature.toJson());
|
|
||||||
|
|
||||||
int messageId = feature.getNumberProperty(MESSAGE_ID).intValue();
|
|
||||||
DcMsg dcMsg = DcHelper.getContext(this).getMsg(messageId);
|
|
||||||
int dcMsgChatId = dcMsg.getChatId();
|
|
||||||
if (dcMsgChatId == DC_CHAT_NO_CHAT) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int startingPosition = DcMsg.getMessagePosition(dcMsg, DcHelper.getContext(this));
|
|
||||||
|
|
||||||
Intent intent = new Intent(MapActivity.this, ConversationActivity.class);
|
|
||||||
intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcMsgChatId);
|
|
||||||
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, startingPosition);
|
|
||||||
startActivity(intent);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean handleAddPoiClick(LatLng point) {
|
|
||||||
if (chatId == DC_CHAT_NO_CHAT || !DcHelper.getContext(this).getChat(chatId).canSend()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (markerViewManager.hasMarkers()) {
|
|
||||||
MarkerView markerView = markerViewManager.getCenteredMarker();
|
|
||||||
if (markerView != null && markerView.getView() instanceof AddPoiView) {
|
|
||||||
AddPoiView view = (AddPoiView) markerView.getView();
|
|
||||||
inputAwareContainer.hideCurrentInput(view.getMessageView());
|
|
||||||
}
|
|
||||||
markerViewManager.removeMarkers();
|
|
||||||
} else {
|
|
||||||
AddPoiView addPoiView = new AddPoiView(this);
|
|
||||||
addPoiView.setLatLng(point);
|
|
||||||
addPoiView.setChatId(chatId);
|
|
||||||
addPoiView.setOnMessageSentListener(markerViewManager);
|
|
||||||
MarkerView markerView = new MarkerView(point, addPoiView);
|
|
||||||
markerViewManager.addMarker(markerView);
|
|
||||||
inputAwareContainer.showSoftkey(addPoiView.getMessageView());
|
|
||||||
markerViewManager.center(markerView);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean handlePoiLongClick(LatLng point) {
|
|
||||||
final PointF pixel = mapboxMap.getProjection().toScreenLocation(point);
|
|
||||||
List<Feature> features = mapboxMap.queryRenderedFeatures(pixel, mapDataManager.getMarkerLayers());
|
|
||||||
for (Feature feature : features) {
|
|
||||||
if (feature.getBooleanProperty(IS_POI)) {
|
|
||||||
new AlertDialog.Builder(MapActivity.this)
|
|
||||||
.setMessage(getString(R.string.menu_delete_location))
|
|
||||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
|
||||||
int messageId = feature.getNumberProperty(MESSAGE_ID).intValue();
|
|
||||||
int[] messages = new int[1];
|
|
||||||
messages[0] = messageId;
|
|
||||||
DcHelper.getContext(MapActivity.this).deleteMsgs(messages);
|
|
||||||
int contactId = feature.getNumberProperty(CONTACT_ID).intValue();
|
|
||||||
if (mapDataManager.isSelected(feature)) {
|
|
||||||
mapDataManager.unselectMarker();
|
|
||||||
}
|
|
||||||
mapDataManager.updateSource(contactId);
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.no, null)
|
|
||||||
.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onKeyboardHidden() {
|
|
||||||
markerViewManager.onKeyboardShown(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onKeyboardShown() {
|
|
||||||
markerViewManager.onKeyboardShown(inputAwareContainer.getKeyboardHeight());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,623 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map;
|
|
||||||
|
|
||||||
import static com.b44t.messenger.DcContext.DC_EVENT_LOCATION_CHANGED;
|
|
||||||
import static com.b44t.messenger.DcContext.DC_GCL_ADD_SELF;
|
|
||||||
import static com.mapbox.mapboxsdk.location.modes.RenderMode.COMPASS;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.all;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.eq;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.has;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.length;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.literal;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.neq;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.not;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.switchCase;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.toBool;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.Property.ICON_ANCHOR_BOTTOM_LEFT;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.Property.NONE;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.Property.TEXT_ANCHOR_CENTER;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.Property.TEXT_ANCHOR_TOP;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.Property.VISIBLE;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAnchor;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconOffset;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.lineColor;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.lineJoin;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.lineOpacity;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.lineWidth;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textAllowOverlap;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textAnchor;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textColor;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textField;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textIgnorePlacement;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textOffset;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textSize;
|
|
||||||
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.visibility;
|
|
||||||
import static org.thoughtcrime.securesms.map.model.MapSource.INFO_WINDOW_LAYER;
|
|
||||||
import static org.thoughtcrime.securesms.map.model.MapSource.LINE_FEATURE_LIST;
|
|
||||||
import static org.thoughtcrime.securesms.util.BitmapUtil.generateColoredBitmap;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.b44t.messenger.DcContext;
|
|
||||||
import com.b44t.messenger.DcEvent;
|
|
||||||
import com.mapbox.geojson.Feature;
|
|
||||||
import com.mapbox.geojson.FeatureCollection;
|
|
||||||
import com.mapbox.mapboxsdk.exceptions.InvalidLatLngBoundsException;
|
|
||||||
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
|
|
||||||
import com.mapbox.mapboxsdk.location.LocationComponent;
|
|
||||||
import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;
|
|
||||||
import com.mapbox.mapboxsdk.location.permissions.PermissionsManager;
|
|
||||||
import com.mapbox.mapboxsdk.maps.Style;
|
|
||||||
import com.mapbox.mapboxsdk.style.expressions.Expression;
|
|
||||||
import com.mapbox.mapboxsdk.style.layers.LineLayer;
|
|
||||||
import com.mapbox.mapboxsdk.style.layers.Property;
|
|
||||||
import com.mapbox.mapboxsdk.style.layers.PropertyFactory;
|
|
||||||
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
|
|
||||||
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
|
|
||||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
|
||||||
import org.thoughtcrime.securesms.map.DataCollectionTask.DataCollectionCallback;
|
|
||||||
import org.thoughtcrime.securesms.map.GenerateInfoWindowTask.GenerateInfoWindowCallback;
|
|
||||||
import org.thoughtcrime.securesms.map.model.FilterProvider;
|
|
||||||
import org.thoughtcrime.securesms.map.model.MapSource;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by cyberta on 07.03.19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MapDataManager implements DcEventCenter.DcEventDelegate,
|
|
||||||
GenerateInfoWindowCallback,
|
|
||||||
DataCollectionCallback {
|
|
||||||
public static final String MARKER_SELECTED = "MARKER_SELECTED";
|
|
||||||
public static final String LAST_LOCATION = "LAST_LOCATION";
|
|
||||||
public static final String CONTACT_ID = "CONTACT_ID";
|
|
||||||
public static final String INFO_WINDOW_ID = "INFO_WINDOW_ID";
|
|
||||||
public static final String TIMESTAMP = "TIMESTAMP";
|
|
||||||
public static final String MESSAGE_ID = "MESSAGE_ID";
|
|
||||||
public static final String ACCURACY = "ACCURACY";
|
|
||||||
public static final String MARKER_CHAR = "MARKER_CHAR";
|
|
||||||
public static final String IS_EMOJI_CHAR = "IS_EMOJI_CHAR";
|
|
||||||
public static final String POI_LONG_DESCRIPTION = "POI_LONG_DESCRIPTION";
|
|
||||||
public static final String MARKER_ICON = "MARKER_ICON";
|
|
||||||
public static final String IS_POI = "IS_POI";
|
|
||||||
public static final String LAST_POSITION_ICON = "LAST_POSITION_ICON";
|
|
||||||
public static final String LAST_POSITION_LABEL = "LAST_POSITION_LABEL";
|
|
||||||
private static final String INFO_WINDOW_SRC = "INFO_WINDOW_SRC";
|
|
||||||
private static final String LAST_POSITION_LAYER = "LAST_POSITION_LAYER";
|
|
||||||
private static final String LAST_POSITION_SOURCE = "LAST_POSITION_SRC";
|
|
||||||
|
|
||||||
public static final int ALL_CHATS_GLOBAL_MAP = 0;
|
|
||||||
public static final long TIMESTAMP_NOW = 0L;
|
|
||||||
public static final long TIME_FRAME = 1000 * 60 * 60 * 24 * 2; // 2d
|
|
||||||
private static final long DEFAULT_LAST_POSITION_DELTA = 1000 * 60 * 60 * 24; // 1d
|
|
||||||
|
|
||||||
private static final String TAG = MapDataManager.class.getSimpleName();
|
|
||||||
private final Style mapboxStyle;
|
|
||||||
private final ConcurrentHashMap<Integer, MapSource> contactMapSources = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentHashMap<String, LinkedList<Feature>> featureCollections = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentHashMap<Integer, Feature> lastPositions = new ConcurrentHashMap<>();
|
|
||||||
private final Set<String> emojiCodePoints = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
|
||||||
private final FilterProvider filterProvider = new FilterProvider();
|
|
||||||
private Feature selectedFeature;
|
|
||||||
private final int chatId;
|
|
||||||
private final LatLngBounds.Builder boundingBuilder;
|
|
||||||
private final Context context;
|
|
||||||
private final DcContext dcContext;
|
|
||||||
private final MapDataState callback;
|
|
||||||
private boolean isInitial = true;
|
|
||||||
private boolean showTraces = false;
|
|
||||||
private final LocationComponent locationComponent;
|
|
||||||
private final EmojiProvider emojiProvider;
|
|
||||||
|
|
||||||
public interface MapDataState {
|
|
||||||
void onDataInitialized(LatLngBounds bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MapDataManager(Context context, @NonNull Style mapboxMapStyle, LocationComponent locationComponent, int chatId, MapDataState updateCallback) {
|
|
||||||
Log.d(TAG, "performance test - create map manager");
|
|
||||||
this.mapboxStyle = mapboxMapStyle;
|
|
||||||
this.context = context;
|
|
||||||
this.dcContext = DcHelper.getContext(context);
|
|
||||||
this.chatId = chatId;
|
|
||||||
boundingBuilder = new LatLngBounds.Builder();
|
|
||||||
this.callback = updateCallback;
|
|
||||||
this.locationComponent = locationComponent;
|
|
||||||
emojiProvider = EmojiProvider.getInstance(context);
|
|
||||||
|
|
||||||
initInfoWindowLayer();
|
|
||||||
initLastPositionLayer();
|
|
||||||
initLocationComponent();
|
|
||||||
|
|
||||||
filterProvider.setMessageFilter(true);
|
|
||||||
filterProvider.setLastPositionFilter(System.currentTimeMillis() - DEFAULT_LAST_POSITION_DELTA);
|
|
||||||
applyLastPositionFilter();
|
|
||||||
|
|
||||||
updateSources();
|
|
||||||
DcHelper.getEventCenter(context).addObserver(DC_EVENT_LOCATION_CHANGED, this);
|
|
||||||
|
|
||||||
Log.d(TAG, "performance test - create map manager finished");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onResume() {
|
|
||||||
DcHelper.getEventCenter(context).addObserver(DC_EVENT_LOCATION_CHANGED, this);
|
|
||||||
if (!isInitial) {
|
|
||||||
updateSources();
|
|
||||||
}
|
|
||||||
isInitial = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPause() {
|
|
||||||
DcHelper.getEventCenter(context).removeObserver(DC_EVENT_LOCATION_CHANGED, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDestroy() {
|
|
||||||
GenerateInfoWindowTask.cancelRunningTasks();
|
|
||||||
DataCollectionTask.cancelRunningTasks();
|
|
||||||
Log.d(TAG, "performance test - Map manager destroyed");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Context getContext() {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refreshSource(int contactId) {
|
|
||||||
MapSource source = contactMapSources.get(contactId);
|
|
||||||
LinkedList<Feature> collection = featureCollections.get(source.getMarkerFeatureCollection());
|
|
||||||
GeoJsonSource pointSource = (GeoJsonSource) mapboxStyle.getSource(source.getMarkerSource());
|
|
||||||
pointSource.setGeoJson(FeatureCollection.fromFeatures(collection));
|
|
||||||
LinkedList<Feature> lineFeatures = featureCollections.get(source.getLineFeatureCollection());
|
|
||||||
GeoJsonSource lineSource = (GeoJsonSource) mapboxStyle.getSource(source.getLineSource());
|
|
||||||
lineSource.setGeoJson(FeatureCollection.fromFeatures(lineFeatures));
|
|
||||||
GeoJsonSource lastPostionSource = (GeoJsonSource) mapboxStyle.getSource(LAST_POSITION_SOURCE);
|
|
||||||
lastPostionSource.setGeoJson(FeatureCollection.fromFeatures(new LinkedList<>(lastPositions.values())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleEvent(@NonNull DcEvent event) {
|
|
||||||
int eventId = event.getId();
|
|
||||||
Log.d(TAG, "updateEvent in MapDataManager called. eventId: " + eventId);
|
|
||||||
int contactId = event.getData1Int();
|
|
||||||
if (contactMapSources.containsKey(contactId)) {
|
|
||||||
HashSet<String> emojiCodePoints = new HashSet<>();
|
|
||||||
DataCollector collector = new DataCollector(dcContext,
|
|
||||||
contactMapSources,
|
|
||||||
featureCollections,
|
|
||||||
lastPositions,
|
|
||||||
emojiCodePoints,
|
|
||||||
emojiProvider,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
collector.updateSource(chatId,
|
|
||||||
contactId,
|
|
||||||
System.currentTimeMillis() - TIME_FRAME,
|
|
||||||
TIMESTAMP_NOW);
|
|
||||||
refreshSource(contactId);
|
|
||||||
handleEmojiCodepoints(emojiCodePoints);
|
|
||||||
}
|
|
||||||
Log.d(TAG, "updateEvent in MapDataManager called. finished: " + eventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean runOnMain() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String[] getMarkerLayers() {
|
|
||||||
String markerLayers[] = new String[contactMapSources.size() + 1];
|
|
||||||
int i = 0;
|
|
||||||
for (Map.Entry<Integer, MapSource> entry : contactMapSources.entrySet()) {
|
|
||||||
markerLayers[i] = entry.getValue().getMarkerLayer();
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
markerLayers[contactMapSources.size()] = LAST_POSITION_LAYER;
|
|
||||||
return markerLayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean unselectMarker() {
|
|
||||||
if (selectedFeature != null) {
|
|
||||||
selectedFeature.addBooleanProperty(MARKER_SELECTED, false);
|
|
||||||
refreshSource(selectedFeature.getNumberProperty(CONTACT_ID).intValue());
|
|
||||||
selectedFeature = null;
|
|
||||||
GeoJsonSource source = (GeoJsonSource) mapboxStyle.getSource(INFO_WINDOW_SRC);
|
|
||||||
source.setGeoJson(FeatureCollection.fromFeatures(new ArrayList<>()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMarkerSelected(String featureId) {
|
|
||||||
if (selectedFeature == null) {
|
|
||||||
setNewMarkerSelected(featureId);
|
|
||||||
} else if (selectedFeature.id().equals(featureId)) {
|
|
||||||
updateSelectedMarker();
|
|
||||||
} else {
|
|
||||||
replaceSelectedMarker(featureId);
|
|
||||||
}
|
|
||||||
|
|
||||||
new GenerateInfoWindowTask(this).execute(selectedFeature);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSelected(@NonNull Feature feature) {
|
|
||||||
return selectedFeature != null && selectedFeature.id().equals(feature.id());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the bitmaps have been generated from a view.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setInfoWindowResults(Bitmap result) {
|
|
||||||
mapboxStyle.addImage(INFO_WINDOW_ID, result);
|
|
||||||
GeoJsonSource infoWindowSource = (GeoJsonSource) mapboxStyle.getSource(INFO_WINDOW_SRC);
|
|
||||||
infoWindowSource.setGeoJson(selectedFeature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDataCollectionFinished(Set <String> emojiCodePoints) {
|
|
||||||
handleEmojiCodepoints(emojiCodePoints);
|
|
||||||
for (MapSource source : contactMapSources.values()) {
|
|
||||||
initContactBasedLayers(source);
|
|
||||||
refreshSource(source.getContactId());
|
|
||||||
applyMarkerFilter(source);
|
|
||||||
applyLineFilter(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boundingBuilder != null && callback != null) {
|
|
||||||
LatLngBounds bound = null;
|
|
||||||
try {
|
|
||||||
bound = boundingBuilder.build();
|
|
||||||
} catch (InvalidLatLngBoundsException e) {
|
|
||||||
Log.w(TAG, e.getLocalizedMessage());
|
|
||||||
}
|
|
||||||
callback.onDataInitialized(bound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleEmojiCodepoints(Set<String> emojiCodePoints) {
|
|
||||||
// generate only new emoji bitmaps, so remove the ones from the set that we already generated
|
|
||||||
emojiCodePoints.removeAll(this.emojiCodePoints);
|
|
||||||
if (emojiCodePoints.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
executor.execute(() -> {
|
|
||||||
for (String codePoint : emojiCodePoints) {
|
|
||||||
Bitmap emoji = emojiProvider.getEmojiBitmap(codePoint, 0.5f, true);
|
|
||||||
handler.post(() -> {
|
|
||||||
mapboxStyle.addImage(codePoint, emoji);
|
|
||||||
emojiCodePoints.add(codePoint);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void filterRange(long startTimestamp, long endTimestamp) {
|
|
||||||
int[] contactIds = getContactIds(chatId);
|
|
||||||
filterProvider.setRangeFilter(startTimestamp, endTimestamp);
|
|
||||||
applyFilters(contactIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void filterLastPositions(long timestamp) {
|
|
||||||
int[] contactIds = getContactIds(chatId);
|
|
||||||
filterProvider.setLastPositionFilter(timestamp);
|
|
||||||
applyFilters(contactIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showTraces(boolean show) {
|
|
||||||
int[] contactIds = getContactIds(chatId);
|
|
||||||
this.showTraces = show;
|
|
||||||
filterProvider.setMessageFilter(!show);
|
|
||||||
applyFilters(contactIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getChatId() {
|
|
||||||
return chatId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAccountId() {
|
|
||||||
return dcContext.getAccountId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showLineLayer(MapSource source) {
|
|
||||||
LineLayer lineLayer = (LineLayer) mapboxStyle.getLayer(source.getLineLayer());
|
|
||||||
if (lineLayer != null) {
|
|
||||||
lineLayer.setProperties(visibility(showTraces ? VISIBLE : NONE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyFilters(int[] contactIds) {
|
|
||||||
for (int contactId : contactIds) {
|
|
||||||
MapSource contactMapMetadata = contactMapSources.get(contactId);
|
|
||||||
if (contactMapMetadata == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
showLineLayer(contactMapMetadata);
|
|
||||||
applyMarkerFilter(contactMapMetadata);
|
|
||||||
applyLineFilter(contactMapMetadata);
|
|
||||||
}
|
|
||||||
applyLastPositionFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyLastPositionFilter() {
|
|
||||||
SymbolLayer markerLayer = (SymbolLayer) mapboxStyle.getLayer(LAST_POSITION_LAYER);
|
|
||||||
if (markerLayer != null) {
|
|
||||||
markerLayer.setFilter(filterProvider.getTimeFilter());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyMarkerFilter(MapSource source) {
|
|
||||||
SymbolLayer markerLayer = (SymbolLayer) mapboxStyle.getLayer(source.getMarkerLayer());
|
|
||||||
if (markerLayer != null) {
|
|
||||||
markerLayer.setFilter(filterProvider.getMarkerFilter());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyLineFilter(MapSource source) {
|
|
||||||
LineLayer lineLayer = (LineLayer) mapboxStyle.getLayer(source.getLineLayer());
|
|
||||||
if (lineLayer != null) {
|
|
||||||
lineLayer.setFilter(filterProvider.getTimeFilter());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int[] getContactIds(int chatId) {
|
|
||||||
if (chatId == ALL_CHATS_GLOBAL_MAP) {
|
|
||||||
return dcContext.getContacts(DC_GCL_ADD_SELF, "");
|
|
||||||
} else {
|
|
||||||
int[] contactIds = dcContext.getChatContacts(chatId);
|
|
||||||
boolean hasSelf = false;
|
|
||||||
for (int contact : contactIds) {
|
|
||||||
if (contact == 1) {
|
|
||||||
hasSelf = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasSelf) {
|
|
||||||
contactIds = Arrays.copyOf(contactIds, contactIds.length + 1);
|
|
||||||
contactIds[contactIds.length - 1] = 1;
|
|
||||||
}
|
|
||||||
return contactIds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initInfoWindowLayer() {
|
|
||||||
Expression iconOffset = switchCase(
|
|
||||||
toBool(get(IS_EMOJI_CHAR)), literal(new Float[] {-2f, -23f}),
|
|
||||||
toBool(get(LAST_LOCATION)), literal(new Float[] {-2f, -25f}),
|
|
||||||
literal(new Float[] {-2f, -20f}));
|
|
||||||
GeoJsonSource infoWindowSource = new GeoJsonSource(INFO_WINDOW_SRC);
|
|
||||||
mapboxStyle.addSource(infoWindowSource);
|
|
||||||
mapboxStyle.addLayer(new SymbolLayer(INFO_WINDOW_LAYER, INFO_WINDOW_SRC).withProperties(
|
|
||||||
iconImage(INFO_WINDOW_ID),
|
|
||||||
iconAnchor(ICON_ANCHOR_BOTTOM_LEFT),
|
|
||||||
/* all info window and marker image to appear at the same time*/
|
|
||||||
iconAllowOverlap(true),
|
|
||||||
/* offset the info window to be above the marker */
|
|
||||||
iconOffset(iconOffset)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initLastPositionLayer() {
|
|
||||||
GeoJsonSource lastPositionSource = new GeoJsonSource(LAST_POSITION_SOURCE);
|
|
||||||
mapboxStyle.addSource(lastPositionSource);
|
|
||||||
Expression markerSize =
|
|
||||||
switchCase(toBool(get(MARKER_SELECTED)), literal(1.75f), literal(1.25f));
|
|
||||||
mapboxStyle.addLayerBelow(new SymbolLayer(LAST_POSITION_LAYER, LAST_POSITION_SOURCE).withProperties(
|
|
||||||
iconImage(get(LAST_POSITION_ICON)),
|
|
||||||
/* all info window and marker image to appear at the same time*/
|
|
||||||
iconAllowOverlap(true),
|
|
||||||
iconIgnorePlacement(true),
|
|
||||||
iconSize(markerSize),
|
|
||||||
textField(get(LAST_POSITION_LABEL)),
|
|
||||||
textAnchor(TEXT_ANCHOR_TOP),
|
|
||||||
textOffset(new Float[]{0.0f, 1.0f}),
|
|
||||||
textAllowOverlap(true),
|
|
||||||
textIgnorePlacement(true)
|
|
||||||
).withFilter(filterProvider.getTimeFilter()), INFO_WINDOW_LAYER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings( {"MissingPermission"})
|
|
||||||
private void initLocationComponent() {
|
|
||||||
if (! PermissionsManager.areLocationPermissionsGranted(context)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LocationComponentActivationOptions locationComponentActivationOptions = LocationComponentActivationOptions
|
|
||||||
.builder(context, mapboxStyle)
|
|
||||||
.build();
|
|
||||||
locationComponent.activateLocationComponent(locationComponentActivationOptions);
|
|
||||||
locationComponent.setRenderMode(COMPASS);
|
|
||||||
locationComponent.setLocationComponentEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initContactBasedLayers(MapSource source) {
|
|
||||||
if (mapboxStyle.getLayer(source.getMarkerLayer()) != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GeoJsonSource markerPositionSource = new GeoJsonSource(source.getMarkerSource());
|
|
||||||
GeoJsonSource linePositionSource = new GeoJsonSource(source.getLineSource());
|
|
||||||
|
|
||||||
try {
|
|
||||||
mapboxStyle.addSource(markerPositionSource);
|
|
||||||
mapboxStyle.addSource(linePositionSource);
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
//TODO: specify exception more
|
|
||||||
Log.e(TAG, "Unable to init GeoJsonSources. Already added to mapBoxMap? " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
mapboxStyle.addImage(source.getMarkerLastPositon(),
|
|
||||||
generateColoredLastPositionIcon(source.getColorArgb()));
|
|
||||||
mapboxStyle.addImage(source.getMarkerIcon(),
|
|
||||||
generateColoredLocationIcon(source.getColorArgb()));
|
|
||||||
mapboxStyle.addImage(source.getMarkerPoi(),
|
|
||||||
generateColoredPoiIcon(source.getColorArgb()));
|
|
||||||
|
|
||||||
Expression markerSize =
|
|
||||||
switchCase(
|
|
||||||
neq(length(get(MARKER_CHAR)), literal(0)),
|
|
||||||
switchCase(toBool(get(MARKER_SELECTED)), literal(2.25f), literal(2.0f)),
|
|
||||||
neq(get(MESSAGE_ID), literal(0)),
|
|
||||||
switchCase(toBool(get(MARKER_SELECTED)), literal(2.25f), literal(2.0f)),
|
|
||||||
switchCase(toBool(get(MARKER_SELECTED)), literal(1.1f), literal(0.7f)));
|
|
||||||
Expression markerIcon = get(MARKER_ICON);
|
|
||||||
|
|
||||||
mapboxStyle.addLayerBelow(new LineLayer(source.getLineLayer(), source.getLineSource())
|
|
||||||
.withProperties(PropertyFactory.lineCap(Property.LINE_CAP_ROUND),
|
|
||||||
lineJoin(Property.LINE_JOIN_ROUND),
|
|
||||||
lineWidth(3f),
|
|
||||||
lineOpacity(0.5f),
|
|
||||||
lineColor(source.getColorArgb()),
|
|
||||||
visibility(NONE)
|
|
||||||
)
|
|
||||||
.withFilter(filterProvider.getTimeFilter()),
|
|
||||||
LAST_POSITION_LAYER);
|
|
||||||
|
|
||||||
|
|
||||||
Expression textField = switchCase(eq(length(get(MARKER_CHAR)), 1), get(MARKER_CHAR),
|
|
||||||
get(POI_LONG_DESCRIPTION));
|
|
||||||
Float[] offset = new Float[] {0.0f, 1.25f};
|
|
||||||
Float[] zeroOffset = new Float[] {0.0f, 0.0f};
|
|
||||||
Expression textOffset = switchCase(
|
|
||||||
has(POI_LONG_DESCRIPTION), literal(offset),
|
|
||||||
literal(zeroOffset));
|
|
||||||
Expression textColor = switchCase(
|
|
||||||
has(POI_LONG_DESCRIPTION), literal("#000000"),
|
|
||||||
literal("#FFFFFF")
|
|
||||||
);
|
|
||||||
Expression textAnchor = switchCase(
|
|
||||||
has(POI_LONG_DESCRIPTION), literal(TEXT_ANCHOR_TOP),
|
|
||||||
literal(TEXT_ANCHOR_CENTER)
|
|
||||||
);
|
|
||||||
Expression textSize = switchCase(
|
|
||||||
has(POI_LONG_DESCRIPTION), literal(12.0f),
|
|
||||||
literal(15.0f)
|
|
||||||
);
|
|
||||||
|
|
||||||
mapboxStyle.addLayerBelow(new SymbolLayer(source.getMarkerLayer(), source.getMarkerSource())
|
|
||||||
.withProperties(
|
|
||||||
iconImage(markerIcon),
|
|
||||||
iconSize(markerSize),
|
|
||||||
iconIgnorePlacement(false),
|
|
||||||
iconAllowOverlap(false),
|
|
||||||
textField(textField),
|
|
||||||
textOffset(textOffset),
|
|
||||||
textAnchor(textAnchor),
|
|
||||||
textSize(textSize),
|
|
||||||
textColor(textColor))
|
|
||||||
.withFilter(all(filterProvider.getMarkerFilter(),
|
|
||||||
not(get(LAST_LOCATION)))),
|
|
||||||
LAST_POSITION_LAYER);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bitmap generateColoredLastPositionIcon(int colorFilter) {
|
|
||||||
return generateColoredBitmap(context, colorFilter, R.drawable.ic_location_on_white_48dp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bitmap generateColoredLocationIcon(int colorFilter) {
|
|
||||||
return generateColoredBitmap(context, colorFilter, R.drawable.ic_location_dot);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bitmap generateColoredPoiIcon(int colorFilter) {
|
|
||||||
return generateColoredBitmap(context, colorFilter, R.drawable.ic_location_poi_dot);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSources() {
|
|
||||||
new DataCollectionTask(dcContext,
|
|
||||||
chatId,
|
|
||||||
getContactIds(chatId),
|
|
||||||
contactMapSources,
|
|
||||||
featureCollections,
|
|
||||||
lastPositions,
|
|
||||||
|
|
||||||
boundingBuilder,
|
|
||||||
emojiProvider,
|
|
||||||
this).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateSource(int contactId) {
|
|
||||||
new DataCollectionTask(dcContext,
|
|
||||||
chatId,
|
|
||||||
new int[]{contactId},
|
|
||||||
contactMapSources,
|
|
||||||
featureCollections,
|
|
||||||
lastPositions,
|
|
||||||
boundingBuilder,
|
|
||||||
emojiProvider,
|
|
||||||
this).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void replaceSelectedMarker(String featureId) {
|
|
||||||
Feature feature = getFeatureWithId(featureId);
|
|
||||||
feature.addBooleanProperty(MARKER_SELECTED, true);
|
|
||||||
selectedFeature.addBooleanProperty(MARKER_SELECTED, false);
|
|
||||||
|
|
||||||
int lastContactId = selectedFeature.getNumberProperty(CONTACT_ID).intValue();
|
|
||||||
int currentContactId = feature.getNumberProperty(CONTACT_ID).intValue();
|
|
||||||
|
|
||||||
selectedFeature = feature;
|
|
||||||
refreshSource(currentContactId);
|
|
||||||
if (lastContactId != currentContactId) {
|
|
||||||
refreshSource(lastContactId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSelectedMarker() {
|
|
||||||
boolean isSelected = selectedFeature.getBooleanProperty(MARKER_SELECTED);
|
|
||||||
selectedFeature.addBooleanProperty(MARKER_SELECTED, !isSelected);
|
|
||||||
refreshSource(selectedFeature.getNumberProperty(CONTACT_ID).intValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNewMarkerSelected(String featureId) {
|
|
||||||
Feature feature = getFeatureWithId(featureId);
|
|
||||||
feature.addBooleanProperty(MARKER_SELECTED, true);
|
|
||||||
selectedFeature = feature;
|
|
||||||
refreshSource(selectedFeature.getNumberProperty(CONTACT_ID).intValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Feature getFeatureWithId(String id) {
|
|
||||||
for (Map.Entry<String, LinkedList<Feature>> e : featureCollections.entrySet()) {
|
|
||||||
String key = e.getKey();
|
|
||||||
if (key.startsWith(LINE_FEATURE_LIST)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
LinkedList<Feature> featureCollection = e.getValue();
|
|
||||||
for (Feature f : featureCollection) {
|
|
||||||
if (f.id().equals(id)) {
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map;
|
|
||||||
|
|
||||||
import android.graphics.PointF;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.mapbox.mapboxsdk.geometry.LatLng;
|
|
||||||
import com.mapbox.mapboxsdk.maps.Projection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MarkerView class wraps a latitude-longitude pair with a Android SDK View.
|
|
||||||
* <p>
|
|
||||||
* It can be used in conjunction with {@link com.mapbox.mapboxsdk.plugins.markerview.MarkerViewManager} to synchronise the Android SDK View on top
|
|
||||||
* of map at the latitude-longitude pair.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class MarkerView {
|
|
||||||
|
|
||||||
private final View view;
|
|
||||||
private LatLng latLng;
|
|
||||||
private Projection projection;
|
|
||||||
private OnPositionUpdateListener onPositionUpdateListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a MarkerView
|
|
||||||
*
|
|
||||||
* @param latLng latitude-longitude pair
|
|
||||||
* @param view an Android SDK View
|
|
||||||
*/
|
|
||||||
public MarkerView(@NonNull LatLng latLng, @NonNull View view) {
|
|
||||||
this.latLng = latLng;
|
|
||||||
this.view = view;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the location of the MarkerView on the map.
|
|
||||||
* <p>
|
|
||||||
* Provided as a latitude-longitude pair.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param latLng latitude-longitude pair
|
|
||||||
*/
|
|
||||||
public void setLatLng(@NonNull LatLng latLng) {
|
|
||||||
this.latLng = latLng;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public LatLng getLatLng() {
|
|
||||||
return latLng;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a callback to be invoked when position placement is calculated.
|
|
||||||
* <p>
|
|
||||||
* Can be used to offset a MarkerView on screen.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param onPositionUpdateListener callback to be invoked when position placement is calculated
|
|
||||||
*/
|
|
||||||
public void setOnPositionUpdateListener(OnPositionUpdateListener onPositionUpdateListener) {
|
|
||||||
this.onPositionUpdateListener = onPositionUpdateListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback definition that is invoked when position placement of a MarkerView is calculated.
|
|
||||||
*/
|
|
||||||
public interface OnPositionUpdateListener {
|
|
||||||
PointF onUpdate(PointF pointF);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setProjection(Projection projection) {
|
|
||||||
this.projection = projection;
|
|
||||||
}
|
|
||||||
|
|
||||||
View getView() {
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
void update() {
|
|
||||||
PointF point = projection.toScreenLocation(latLng);
|
|
||||||
if (onPositionUpdateListener != null) {
|
|
||||||
point = onPositionUpdateListener.onUpdate(point);
|
|
||||||
}
|
|
||||||
view.setX(point.x);
|
|
||||||
view.setY(point.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.UiThread;
|
|
||||||
|
|
||||||
import com.mapbox.mapboxsdk.camera.CameraPosition;
|
|
||||||
import com.mapbox.mapboxsdk.camera.CameraUpdate;
|
|
||||||
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
|
|
||||||
import com.mapbox.mapboxsdk.maps.MapView;
|
|
||||||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class responsible for synchronising views at a LatLng on top of a Map.
|
|
||||||
*/
|
|
||||||
public class MarkerViewManager implements MapView.OnDidFinishRenderingFrameListener, SendingTask.OnMessageSentListener {
|
|
||||||
|
|
||||||
private final MapView mapView;
|
|
||||||
private final MapboxMap mapboxMap;
|
|
||||||
private final List<MarkerView> markers = new ArrayList<>();
|
|
||||||
private boolean initialised;
|
|
||||||
private MarkerView centeredMarker;
|
|
||||||
private int keyboardHeight = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a MarkerViewManager.
|
|
||||||
*
|
|
||||||
* @param mapView the MapView used to synchronise views on
|
|
||||||
* @param mapboxMap the MapboxMap to synchronise views with
|
|
||||||
*/
|
|
||||||
public MarkerViewManager(MapView mapView, MapboxMap mapboxMap) {
|
|
||||||
this.mapView = mapView;
|
|
||||||
this.mapboxMap = mapboxMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys the MarkerViewManager.
|
|
||||||
* <p>
|
|
||||||
* Should be called before MapView#onDestroy
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
public void onDestroy() {
|
|
||||||
markers.clear();
|
|
||||||
mapView.removeOnDidFinishRenderingFrameListener(this);
|
|
||||||
initialised = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a MarkerView to the map using MarkerView and LatLng.
|
|
||||||
*
|
|
||||||
* @param markerView the markerView to synchronise on the map
|
|
||||||
*/
|
|
||||||
@UiThread
|
|
||||||
public void addMarker(@NonNull MarkerView markerView) {
|
|
||||||
if (mapView.isDestroyed() || markers.contains(markerView)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!initialised) {
|
|
||||||
initialised = true;
|
|
||||||
mapView.addOnDidFinishRenderingFrameListener(this);
|
|
||||||
}
|
|
||||||
markerView.setProjection(mapboxMap.getProjection());
|
|
||||||
mapView.addView(markerView.getView());
|
|
||||||
markers.add(markerView);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasMarkers() {
|
|
||||||
return markers.size() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
public void removeMarkers() {
|
|
||||||
if (!mapView.isDestroyed()) {
|
|
||||||
for (MarkerView markerView : markers) {
|
|
||||||
mapView.removeView(markerView.getView());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
centeredMarker = null;
|
|
||||||
markers.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDidFinishRenderingFrame(boolean fully) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update() {
|
|
||||||
for (MarkerView marker : markers) {
|
|
||||||
marker.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessageSent() {
|
|
||||||
removeMarkers();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
public void center(MarkerView view) {
|
|
||||||
centeredMarker = view;
|
|
||||||
view.getView().post(() -> {
|
|
||||||
int markerWidth = view.getView().getWidth();
|
|
||||||
CameraPosition currentPosition = mapboxMap.getCameraPosition();
|
|
||||||
CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(
|
|
||||||
new CameraPosition.Builder().
|
|
||||||
target(view.getLatLng()).
|
|
||||||
bearing(currentPosition.bearing).
|
|
||||||
tilt(currentPosition.tilt).
|
|
||||||
zoom(currentPosition.zoom).
|
|
||||||
padding(0d , 0d, markerWidth / 2d, keyboardHeight).
|
|
||||||
build());
|
|
||||||
mapboxMap.easeCamera(cameraUpdate);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
MarkerView getCenteredMarker() {
|
|
||||||
return centeredMarker;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
void onKeyboardShown(int keyboardHeight) {
|
|
||||||
this.keyboardHeight = keyboardHeight;
|
|
||||||
if (centeredMarker != null) {
|
|
||||||
center(centeredMarker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import com.b44t.messenger.DcContext;
|
|
||||||
import com.b44t.messenger.DcMsg;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
|
||||||
|
|
||||||
public class SendingTask extends AsyncTask<SendingTask.Model, SendingTask.Model, SendingTask.Model> {
|
|
||||||
|
|
||||||
public interface OnMessageSentListener {
|
|
||||||
void onMessageSent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Model {
|
|
||||||
private final DcMsg msg;
|
|
||||||
private final int chatId;
|
|
||||||
private final OnMessageSentListener callback;
|
|
||||||
|
|
||||||
public Model(DcMsg msg,
|
|
||||||
int chatId,
|
|
||||||
OnMessageSentListener callback) {
|
|
||||||
this.msg = msg;
|
|
||||||
this.chatId = chatId;
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final DcContext dcContext;
|
|
||||||
|
|
||||||
public SendingTask(Context context) {
|
|
||||||
this.dcContext = DcHelper.getContext(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Model doInBackground(Model... param) {
|
|
||||||
Model m = param[0];
|
|
||||||
if(m.msg!=null) {
|
|
||||||
dcContext.sendMsg(m.chatId, m.msg);
|
|
||||||
}
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Model result) {
|
|
||||||
if (result.callback != null) {
|
|
||||||
result.callback.onMessageSent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map.model;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.mapbox.mapboxsdk.style.expressions.Expression;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.all;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.gte;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.literal;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.lte;
|
|
||||||
import static com.mapbox.mapboxsdk.style.expressions.Expression.neq;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.MESSAGE_ID;
|
|
||||||
import static org.thoughtcrime.securesms.map.MapDataManager.TIMESTAMP;
|
|
||||||
import static org.thoughtcrime.securesms.map.model.FilterProvider.FilterType.LAST_POSITION;
|
|
||||||
import static org.thoughtcrime.securesms.map.model.FilterProvider.FilterType.MESSAGES;
|
|
||||||
import static org.thoughtcrime.securesms.map.model.FilterProvider.FilterType.RANGE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by cyberta on 11.04.19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class FilterProvider {
|
|
||||||
public enum FilterType {
|
|
||||||
LAST_POSITION,
|
|
||||||
RANGE,
|
|
||||||
MESSAGES
|
|
||||||
}
|
|
||||||
|
|
||||||
private final HashMap<FilterType, Expression> expressions = new HashMap();
|
|
||||||
|
|
||||||
|
|
||||||
public void setRangeFilter(long startTimestamp, long endTimestamp) {
|
|
||||||
removeFilter(LAST_POSITION);
|
|
||||||
addFilter(RANGE, all(
|
|
||||||
lte(get(TIMESTAMP), endTimestamp),
|
|
||||||
gte(get(TIMESTAMP), startTimestamp)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastPositionFilter(long startTimestamp) {
|
|
||||||
removeFilter(RANGE);
|
|
||||||
addFilter(LAST_POSITION, gte(get(TIMESTAMP), startTimestamp));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMessageFilter(boolean filter) {
|
|
||||||
if (filter) {
|
|
||||||
addFilter(MESSAGES, neq(get(MESSAGE_ID), literal(0)));
|
|
||||||
} else {
|
|
||||||
removeFilter(MESSAGES);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addFilter(FilterType type, @NonNull Expression expression) {
|
|
||||||
expressions.put(type, expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeFilter(FilterType type) {
|
|
||||||
expressions.remove(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Expression getMarkerFilter() {
|
|
||||||
return all(expressions.values().toArray(new Expression[expressions.values().size()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Expression getTimeFilter() {
|
|
||||||
if (expressions.get(LAST_POSITION) != null) {
|
|
||||||
return expressions.get(LAST_POSITION);
|
|
||||||
} else if (expressions.get(RANGE) != null) {
|
|
||||||
return expressions.get(RANGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return all();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.map.model;
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
|
|
||||||
import com.b44t.messenger.DcContact;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by cyberta on 07.03.19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MapSource {
|
|
||||||
public static final String LINE_LAYER = "line_layer";
|
|
||||||
public static final String MARKER_LAYER = "symbol_layer";
|
|
||||||
public static final String INFO_WINDOW_LAYER = "info_window_layer";
|
|
||||||
public static final String LINE_SOURCE = "line_source";
|
|
||||||
public static final String MARKER_POSITION_SOURCE = "marker_position";
|
|
||||||
public static final String MARKER_ICON = "marker_icon_id";
|
|
||||||
public static final String MARKER_POI = "marker_poi";
|
|
||||||
public static final String MARKER_LAST_POSITON = "marker_last_position";
|
|
||||||
public static final String MARKER_FEATURE_LIST = "marker_feature_list";
|
|
||||||
public static final String LINE_FEATURE_LIST = "line_feature_list";
|
|
||||||
|
|
||||||
private final String markerSource;
|
|
||||||
private final String lineSource;
|
|
||||||
|
|
||||||
private final String markerLayer;
|
|
||||||
private final String lineLayer;
|
|
||||||
|
|
||||||
private final String markerIcon;
|
|
||||||
private final String markerLastPositon;
|
|
||||||
private final String markerPoi;
|
|
||||||
private final String markerFeatureCollection;
|
|
||||||
private final String lineFeatureCollection;
|
|
||||||
private final String displayName;
|
|
||||||
|
|
||||||
private final int color;
|
|
||||||
private final int colorArgb;
|
|
||||||
private final int contactId;
|
|
||||||
|
|
||||||
public MapSource(DcContact contact) {
|
|
||||||
int contactId = contact.getId();
|
|
||||||
markerSource = MARKER_POSITION_SOURCE + "_" + contactId;
|
|
||||||
lineSource = LINE_SOURCE + "_" + contactId;
|
|
||||||
markerLayer = MARKER_LAYER + "_" + contactId;
|
|
||||||
lineLayer = LINE_LAYER + "_" + contactId;
|
|
||||||
markerIcon = MARKER_ICON + "_" + contactId;
|
|
||||||
markerLastPositon = MARKER_LAST_POSITON + "_" + contactId;
|
|
||||||
markerPoi = MARKER_POI + "_" + contactId;
|
|
||||||
markerFeatureCollection = MARKER_FEATURE_LIST + "_" + contactId;
|
|
||||||
lineFeatureCollection = LINE_FEATURE_LIST + "_" + contactId;
|
|
||||||
this.contactId = contactId;
|
|
||||||
displayName = contact.getDisplayName();
|
|
||||||
color = contact.getColor();
|
|
||||||
colorArgb = Color.argb(0xFF, Color.red(color), Color.green(color), Color.blue(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getColorArgb() {
|
|
||||||
return colorArgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getColor() {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMarkerSource() {
|
|
||||||
return markerSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLineSource() {
|
|
||||||
return lineSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMarkerLayer() {
|
|
||||||
return markerLayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLineLayer() {
|
|
||||||
return lineLayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMarkerIcon() {
|
|
||||||
return markerIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMarkerLastPositon() {
|
|
||||||
return markerLastPositon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMarkerPoi() {
|
|
||||||
return markerPoi;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMarkerFeatureCollection() { return markerFeatureCollection; }
|
|
||||||
|
|
||||||
public String getLineFeatureCollection() { return lineFeatureCollection; }
|
|
||||||
|
|
||||||
public int getContactId() { return contactId; }
|
|
||||||
|
|
||||||
public String getDisplayName() {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,7 +1,5 @@
|
||||||
package org.thoughtcrime.securesms.util;
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
import static com.mapbox.mapboxsdk.constants.MapboxConstants.MINIMUM_ZOOM;
|
|
||||||
|
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -16,7 +14,6 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
import com.b44t.messenger.DcContext;
|
import com.b44t.messenger.DcContext;
|
||||||
import com.mapbox.mapboxsdk.geometry.LatLng;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
|
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
|
||||||
|
@ -64,10 +61,6 @@ public class Prefs {
|
||||||
|
|
||||||
private static final String PREF_CONTACT_PHOTO_IDENTIFIERS = "pref_contact_photo_identifiers";
|
private static final String PREF_CONTACT_PHOTO_IDENTIFIERS = "pref_contact_photo_identifiers";
|
||||||
|
|
||||||
private static final String MAP_CENTER_LATITUDE = "pref_map_center_latitude";
|
|
||||||
private static final String MAP_CENTER_LONGITUDE = "pref_map_center_longitude";
|
|
||||||
private static final String MAP_ZOOM = "pref_map_zoom";
|
|
||||||
|
|
||||||
public static final String ALWAYS_LOAD_REMOTE_CONTENT = "pref_always_load_remote_content";
|
public static final String ALWAYS_LOAD_REMOTE_CONTENT = "pref_always_load_remote_content";
|
||||||
public static final boolean ALWAYS_LOAD_REMOTE_CONTENT_DEFAULT = false;
|
public static final boolean ALWAYS_LOAD_REMOTE_CONTENT_DEFAULT = false;
|
||||||
|
|
||||||
|
@ -269,31 +262,6 @@ public class Prefs {
|
||||||
return getStringPreference(context, LED_COLOR_PREF, "blue");
|
return getStringPreference(context, LED_COLOR_PREF, "blue");
|
||||||
}
|
}
|
||||||
|
|
||||||
// map
|
|
||||||
|
|
||||||
public static void setMapCenter(Context context, int accountId, int chatId, LatLng latLng) {
|
|
||||||
setLongPreference(context, MAP_CENTER_LATITUDE+accountId+"."+chatId, Double.doubleToRawLongBits(latLng.getLatitude()));
|
|
||||||
setLongPreference(context, MAP_CENTER_LONGITUDE+accountId+"."+chatId, Double.doubleToRawLongBits(latLng.getLongitude()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setMapZoom(Context context, int accountId, int chatId, double zoom) {
|
|
||||||
setLongPreference(context, MAP_ZOOM+accountId+"."+chatId, Double.doubleToRawLongBits(zoom));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LatLng getMapCenter(Context context, int accountId, int chatId) {
|
|
||||||
long latitude = getLongPreference(context, MAP_CENTER_LATITUDE+accountId+"."+chatId, Long.MAX_VALUE);
|
|
||||||
long longitude = getLongPreference(context, MAP_CENTER_LONGITUDE+accountId+"."+chatId, Long.MAX_VALUE);
|
|
||||||
if (latitude == Long.MAX_VALUE || longitude == Long.MAX_VALUE) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new LatLng(Double.longBitsToDouble(latitude), Double.longBitsToDouble(longitude));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double getMapZoom(Context context, int accountId, int chatId) {
|
|
||||||
long zoom = getLongPreference(context, MAP_ZOOM+accountId+"."+chatId, Double.doubleToLongBits(MINIMUM_ZOOM));
|
|
||||||
return Double.longBitsToDouble(zoom);
|
|
||||||
}
|
|
||||||
|
|
||||||
// misc.
|
// misc.
|
||||||
|
|
||||||
public static String getBackgroundImagePath(Context context, int accountId) {
|
public static String getBackgroundImagePath(Context context, int accountId) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue