Package structure: moved Marker UI code.

Part of #963.
This commit is contained in:
Dennis Guse
2022-01-15 20:24:19 +01:00
parent 1a125ca60d
commit 2ca69df447
14 changed files with 29 additions and 26 deletions
@@ -0,0 +1,95 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.ui.markers;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.DialogUtils;
/**
* A DialogFragment to delete marker.
*
* @author Jimmy Shih
*/
public class DeleteMarkerDialogFragment extends DialogFragment {
private static final String DELETE_MARKER_DIALOG_TAG = "deleteMarkerDialog";
private static final String KEY_MARKER_IDS = "markerIds";
private DeleteMarkerCaller caller;
public static void showDialog(FragmentManager fragmentManager, Marker.Id... markerIds) {
Bundle bundle = new Bundle();
bundle.putParcelableArray(KEY_MARKER_IDS, markerIds);
DeleteMarkerDialogFragment deleteMarkerDialogFragment = new DeleteMarkerDialogFragment();
deleteMarkerDialogFragment.setArguments(bundle);
deleteMarkerDialogFragment.show(fragmentManager, DELETE_MARKER_DIALOG_TAG);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
caller = (DeleteMarkerCaller) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement " + DeleteMarkerCaller.class.getSimpleName());
}
}
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Marker.Id[] markerIds = (Marker.Id[]) getArguments().getParcelableArray(KEY_MARKER_IDS);
final Context context = getContext();
final FragmentActivity fragmentActivity = getActivity();
int titleId = markerIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.marker_delete_one_confirm_title;
int messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message;
return DialogUtils.createConfirmationDialog(
fragmentActivity, titleId, getString(messageId), (dialog, which) -> new Thread(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
for (Marker.Id markerId : markerIds) {
contentProviderUtils.deleteMarker(context, markerId);
}
caller.onMarkerDeleted();
}).start());
}
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface DeleteMarkerCaller {
/**
* Called when delete marker is done.
*/
void onMarkerDeleted();
}
}
@@ -0,0 +1,157 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.ui.markers;
import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.databinding.MarkerDetailActivityBinding;
import de.dennisguse.opentracks.ui.markers.DeleteMarkerDialogFragment.DeleteMarkerCaller;
/**
* An activity to display marker detail info.
* <p>
* Allows to swipe to the next and previous marker.
*
* @author Leif Hendrik Wilden
*/
public class MarkerDetailActivity extends AbstractActivity implements DeleteMarkerCaller {
public static final String EXTRA_MARKER_ID = "marker_id";
private static final String TAG = MarkerDetailActivity.class.getSimpleName();
private MarkerDetailActivityBinding viewBinding;
private List<Marker.Id> markerIds;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
Object bundleMarkerId = getIntent().getExtras().get(EXTRA_MARKER_ID);
Marker.Id markerId = null;
if (bundleMarkerId instanceof Marker.Id) {
markerId = (Marker.Id) bundleMarkerId;
}
if (bundleMarkerId instanceof Long) {
//Incoming Intent via Dashboard API.
markerId = new Marker.Id((Long) bundleMarkerId);
}
if (markerId == null) {
Log.d(TAG, "invalid marker id");
finish();
return;
}
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Marker marker = contentProviderUtils.getMarker(markerId);
markerIds = new ArrayList<>();
int markerIndex = -1;
//TODO Load only markerIds, not the whole marker
try (Cursor cursor = contentProviderUtils.getMarkerCursor(marker.getTrackId(), null, -1)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
Marker currentMarker = contentProviderUtils.createMarker(cursor);
markerIds.add(currentMarker.getId());
if (markerId.equals(currentMarker.getId())) {
markerIndex = markerIds.size() - 1;
}
cursor.moveToNext();
}
}
}
final MarkerDetailPagerAdapter markerAdapter = new MarkerDetailPagerAdapter(getSupportFragmentManager());
viewBinding.makerDetailActivityViewPager.setAdapter(markerAdapter);
viewBinding.makerDetailActivityViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
setTitle(markerAdapter.getPageTitle(position));
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
viewBinding.makerDetailActivityViewPager.setCurrentItem(markerIndex == -1 ? 0 : markerIndex);
}
@Override
protected View getRootView() {
viewBinding = MarkerDetailActivityBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@Override
public void onMarkerDeleted() {
runOnUiThread(this::finish);
}
/**
* Marker detail pager adapter.
*
* @author Jimmy Shih
*/
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
@Override
@NonNull
public Fragment getItem(int position) {
return MarkerDetailFragment.newInstance(markerIds.get(position));
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return getString(R.string.marker_title, position + 1, getCount());
}
@Override
public int getCount() {
return markerIds.size();
}
}
}
@@ -0,0 +1,261 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.ui.markers;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import java.time.Duration;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.databinding.MarkerDetailFragmentBinding;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.StringUtils;
/**
* A fragment to show marker details.
*
* @author Jimmy Shih
*/
public class MarkerDetailFragment extends Fragment {
private static final String TAG = MarkerDetailFragment.class.getSimpleName();
private static final String KEY_MARKER_ID = "markerId";
private static final Duration HIDE_TEXT_DELAY = Duration.ofSeconds(4);
private MenuItem shareMarkerImageMenuItem;
private ContentProviderUtils contentProviderUtils;
private Handler handler;
private Marker.Id markerId;
private Marker marker;
private MarkerDetailFragmentBinding viewBinding;
private final Runnable hideText = new Runnable() {
@Override
public void run() {
Animation animation = AnimationUtils.loadAnimation(getActivity(), R.anim.fadeout);
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation anim) {
}
@Override
public void onAnimationRepeat(Animation anim) {
}
@Override
public void onAnimationEnd(Animation anim) {
viewBinding.markerDetailMarkerTextGradient.setVisibility(View.GONE);
viewBinding.markerDetailMarkerInfo.setVisibility(View.GONE);
}
});
viewBinding.markerDetailMarkerTextGradient.startAnimation(animation);
viewBinding.markerDetailMarkerInfo.startAnimation(animation);
}
};
public static MarkerDetailFragment newInstance(Marker.Id markerId) {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_MARKER_ID, markerId);
MarkerDetailFragment fragment = new MarkerDetailFragment();
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
markerId = getArguments().getParcelable(KEY_MARKER_ID);
if (markerId == null) {
Log.d(TAG, "invalid marker id");
getParentFragmentManager().popBackStack();
return;
}
contentProviderUtils = new ContentProviderUtils(getActivity());
handler = new Handler();
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
viewBinding = MarkerDetailFragmentBinding.inflate(inflater, container, false);
viewBinding.markerDetailMarkerPhoto.setOnClickListener(v -> {
handler.removeCallbacks(hideText);
int visibility = viewBinding.markerDetailMarkerInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
viewBinding.markerDetailMarkerTextGradient.setVisibility(visibility);
viewBinding.markerDetailMarkerInfo.setVisibility(visibility);
if (visibility == View.VISIBLE) {
handler.postDelayed(hideText, HIDE_TEXT_DELAY.toMillis());
}
});
return viewBinding.getRoot();
}
@Override
public void onResume() {
super.onResume();
// Need to update the marker in case returning after an edit
updateMarker(true);
updateUi();
updateMenuItems();
}
@Override
public void onPause() {
super.onPause();
handler.removeCallbacks(hideText);
}
@Override
public void onDestroyView() {
super.onDestroyView();
viewBinding = null;
}
@Override
public void onDestroy() {
super.onDestroy();
markerId = null;
marker = null;
}
@Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
// View pager caches the neighboring fragments in the resumed state.
// If becoming visible from the resumed state, update the UI to display the text above the image.
if (isResumed()) {
if (menuVisible) {
updateUi();
} else {
handler.removeCallbacks(hideText);
}
}
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.marker_detail, menu);
shareMarkerImageMenuItem = menu.findItem(R.id.marker_detail_share);
updateMarker(false);
updateMenuItems();
}
private void updateMenuItems() {
if (shareMarkerImageMenuItem != null)
shareMarkerImageMenuItem.setVisible(marker.hasPhoto());
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
FragmentActivity fragmentActivity = getActivity();
if (item.getItemId() == R.id.marker_detail_show_on_map) {
IntentUtils.showCoordinateOnMap(getContext(), marker);
return true;
}
if (item.getItemId() == R.id.marker_detail_edit) {
Intent intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
return true;
}
if (item.getItemId() == R.id.marker_detail_share) {
if (marker.hasPhoto()) {
Intent intent = IntentUtils.newShareFileIntent(getContext(), marker.getId());
intent = Intent.createChooser(intent, null);
startActivity(intent);
}
return true;
}
if (item.getItemId() == R.id.marker_detail_delete) {
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), markerId);
return true;
}
return super.onOptionsItemSelected(item);
}
private void updateMarker(boolean refresh) {
if (refresh || marker == null) {
marker = contentProviderUtils.getMarker(markerId);
if (marker == null) {
Log.d(TAG, "marker is null");
getParentFragmentManager().popBackStack();
}
}
}
private void updateUi() {
boolean hasPhoto = marker.hasPhoto();
if (hasPhoto) {
handler.removeCallbacks(hideText);
viewBinding.markerDetailMarkerPhoto.setImageURI(marker.getPhotoURI());
handler.postDelayed(hideText, HIDE_TEXT_DELAY.toMillis());
} else {
viewBinding.markerDetailMarkerPhoto.setImageResource(MarkerUtils.ICON_ID);
}
ListItemUtils.setTextView(getActivity(), viewBinding.markerDetailMarkerName, marker.getName(), hasPhoto);
ListItemUtils.setTextView(getActivity(), viewBinding.markerDetailMarkerCategory, StringUtils.getCategory(marker.getCategory()), hasPhoto);
ListItemUtils.setTextView(getActivity(), viewBinding.markerDetailMarkerDescription, marker.getDescription(), hasPhoto);
setLocation(hasPhoto);
}
private void setLocation(boolean addShadow) {
String value = "[" + getString(R.string.stats_latitude) + " "
+ StringUtils.formatCoordinate(getContext(), marker.getLatitude()) + ", "
+ getString(R.string.stats_longitude) + " "
+ StringUtils.formatCoordinate(getContext(), marker.getLongitude()) + "]";
ListItemUtils.setTextView(getActivity(), viewBinding.markerDetailMarkerLocation, value, addShadow);
}
}
@@ -0,0 +1,225 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.ui.markers;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModelProvider;
import java.io.FileDescriptor;
import java.io.IOException;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.databinding.MarkerEditBinding;
import de.dennisguse.opentracks.util.IntentUtils;
/**
* An activity to add/edit a marker.
*
* @author Jimmy Shih
*/
public class MarkerEditActivity extends AbstractActivity {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
private static final int CAMERA_REQUEST_CODE = 5;
private static final int GALLERY_IMG_REQUEST_CODE = 7;
private static final String TAG = MarkerEditActivity.class.getSimpleName();
private Track.Id trackId;
private Marker marker;
private MenuItem insertPhotoMenuItem;
private MenuItem insertGalleryImgMenuItem;
private boolean hasCamera;
private Uri cameraPhotoUri;
private MarkerEditViewModel viewModel;
// UI elements
private MarkerEditBinding viewBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
// Setup UI elements
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.marker_types, android.R.layout.simple_dropdown_item_1line);
viewBinding.markerEditMarkerType.setAdapter(adapter);
viewBinding.markerEditPhotoDelete.setOnClickListener(v -> viewModel.onPhotoDelete(viewBinding.markerEditName.getText().toString(),
viewBinding.markerEditMarkerType.getText().toString(),
viewBinding.markerEditDescription.getText().toString()));
viewBinding.markerEditCancel.setOnClickListener(v -> {
viewModel.onCancel();
finish();
});
boolean isNewMarker = markerId == null;
setTitle(isNewMarker ? R.string.menu_insert_marker : R.string.menu_edit);
viewBinding.markerEditDone.setText(isNewMarker ? R.string.generic_add : R.string.generic_save);
viewBinding.markerEditDone.setOnClickListener(v -> {
viewModel.onDone(viewBinding.markerEditName.getText().toString(),
viewBinding.markerEditMarkerType.getText().toString(),
viewBinding.markerEditDescription.getText().toString());
finish();
});
viewModel = new ViewModelProvider(this).get(MarkerEditViewModel.class);
viewModel.getMarkerData(trackId, markerId).observe(this, data -> {
marker = data;
viewBinding.markerEditName.setText(marker.getName());
viewBinding.markerEditMarkerType.setText(marker.getCategory());
viewBinding.markerEditDescription.setText(marker.getDescription());
if (marker.hasPhoto()) {
setMarkerImageView(marker.getPhotoURI());
} else {
viewBinding.markerEditPhoto.setImageDrawable(null);
}
hideAndShowOptions();
});
}
@Override
protected View getRootView() {
viewBinding = MarkerEditBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.marker_edit, menu);
insertPhotoMenuItem = menu.findItem(R.id.marker_edit_insert_photo);
insertPhotoMenuItem.setVisible(hasCamera);
insertGalleryImgMenuItem = menu.findItem(R.id.marker_edit_insert_gallery_img);
hideAndShowOptions();
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.marker_edit_insert_photo) {
createMarkerWithPicture();
return true;
}
if (item.getItemId() == R.id.marker_edit_insert_gallery_img) {
createMarkerWithGalleryImage();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAMERA_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
return;
} else if (resultCode == RESULT_OK) {
viewModel.onNewCameraPhoto(cameraPhotoUri,
viewBinding.markerEditName.getText().toString(),
viewBinding.markerEditMarkerType.getText().toString(),
viewBinding.markerEditDescription.getText().toString());
}
} else if (requestCode == GALLERY_IMG_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
return;
} else if (resultCode == RESULT_OK) {
viewModel.onNewGalleryPhoto(data.getData(),
viewBinding.markerEditName.getText().toString(),
viewBinding.markerEditMarkerType.getText().toString(),
viewBinding.markerEditDescription.getText().toString());
}
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* Checks and hide/shows all buttons/options about marker photo options.
* <p>
* If a photo is set then one's options are shown, otherwise another ones are shown.
*/
private void hideAndShowOptions() {
boolean isPhotoSet = (marker != null && marker.hasPhoto());
if (insertPhotoMenuItem != null && insertGalleryImgMenuItem != null) {
insertPhotoMenuItem.setVisible(!isPhotoSet);
insertGalleryImgMenuItem.setVisible(!isPhotoSet);
}
viewBinding.markerEditPhotoDelete.setVisibility(isPhotoSet ? View.VISIBLE : View.GONE);
}
/**
* Returns the trackId; either from track or marker.
*/
private Track.Id getTrackId() {
return trackId == null ? marker.getTrackId() : trackId;
}
private void setMarkerImageView(@NonNull Uri uri) {
try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r")) {
FileDescriptor fd = pfd.getFileDescriptor();
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
viewBinding.markerEditPhoto.setImageBitmap(bitmap);
hideAndShowOptions();
} catch (IOException e) {
Log.e(TAG, e.getMessage());
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
}
}
private void createMarkerWithPicture() {
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, getTrackId());
cameraPhotoUri = intentAndPhotoUri.second;
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
}
private void createMarkerWithGalleryImage() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
}
}
@@ -0,0 +1,177 @@
package de.dennisguse.opentracks.ui.markers;
import android.app.Application;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.time.Instant;
import java.util.NoSuchElementException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.FileUtils;
public class MarkerEditViewModel extends AndroidViewModel {
private static final String TAG = MarkerEditViewModel.class.getSimpleName();
private MutableLiveData<Marker> markerData;
private boolean isNewMarker;
private Uri photoOriginalUri;
private final TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
public MarkerEditViewModel(@NonNull Application application) {
super(application);
}
public LiveData<Marker> getMarkerData(@NonNull Track.Id trackId, @Nullable Marker.Id markerId) {
if (markerData == null) {
markerData = new MutableLiveData<>();
trackRecordingServiceConnection.startConnection(getApplication());
loadData(trackId, markerId);
}
return markerData;
}
@Override
protected void onCleared() {
super.onCleared();
trackRecordingServiceConnection.unbind(getApplication());
}
private void loadData(Track.Id trackId, Marker.Id markerId) {
Marker marker;
isNewMarker = markerId == null;
if (isNewMarker) {
int nextMarkerNumber = trackId == null ? 0 : new ContentProviderUtils(getApplication()).getNextMarkerNumber(trackId);
marker = new Marker(trackId, (Instant) null);
marker.setName(getApplication().getString(R.string.marker_name_format, nextMarkerNumber));
} else {
marker = new ContentProviderUtils(getApplication()).getMarker(markerId);
if (marker.hasPhoto()) {
photoOriginalUri = marker.getPhotoURI();
}
}
markerData.postValue(marker);
}
private @NonNull Marker getMarker() throws NoSuchElementException {
Marker marker = markerData != null ? markerData.getValue() : null;
if (marker == null) {
Log.d(TAG, "Marker data shouldn't be null. Call getMarkerData before.");
throw new NoSuchElementException("Marker data shouldn't be null. Call getMarkerData before.");
}
return marker;
}
private void deletePhoto(@Nullable Uri photoUri) {
if (photoUri == null) {
return;
}
File photoFile = FileUtils.getPhotoFileIfExists(getApplication(), markerData.getValue().getTrackId(), photoUri);
if (photoFile != null) {
FileUtils.deleteDirectoryRecurse(photoFile);
}
}
private void deletePhoto(Marker marker) {
if (marker.hasPhoto()) {
deletePhoto(marker.getPhotoURI());
}
}
public void onPhotoDelete(String name, String category, String description) {
Marker marker = getMarker();
if (marker.hasPhoto()) {
if (!marker.getPhotoURI().equals(photoOriginalUri)) {
deletePhoto(marker.getPhotoURI());
}
marker.setPhotoUrl(null);
marker.setName(name);
marker.setCategory(category);
marker.setDescription(description);
markerData.postValue(marker);
}
}
public void onNewCameraPhoto(@NonNull Uri photoUri, String name, String category, String description) {
Marker marker = getMarker();
marker.setPhotoUrl(photoUri.toString());
marker.setName(name);
marker.setCategory(category);
marker.setDescription(description);
markerData.postValue(marker);
}
public void onNewGalleryPhoto(@NonNull Uri srcUri, String name, String category, String description) {
Marker marker = getMarker();
try (ParcelFileDescriptor parcelFd = getApplication().getContentResolver().openFileDescriptor(srcUri, "r")) {
FileDescriptor srcFd = parcelFd.getFileDescriptor();
File dstFile = new File(FileUtils.getImageUrl(getApplication(), marker.getTrackId()));
FileUtils.copy(srcFd, dstFile);
Uri photoUri = FileUtils.getUriForFile(getApplication(), dstFile);
marker.setPhotoUrl(photoUri.toString());
marker.setName(name);
marker.setCategory(category);
marker.setDescription(description);
markerData.postValue(marker);
} catch (IOException e) {
Log.e(TAG, e.getMessage());
Toast.makeText(getApplication(), R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
}
}
private void onAddDone(@NonNull Marker marker, String name, String category, String description) {
trackRecordingServiceConnection.addMarker(getApplication(), name, category, description, marker.hasPhoto() ? marker.getPhotoURI().toString() : null);
}
private void onSaveDone(@NonNull Marker marker, String name, String category, String description) {
marker.setName(name);
marker.setCategory(category);
marker.setDescription(description);
new ContentProviderUtils(getApplication()).updateMarker(getApplication(), marker);
if (photoOriginalUri != null && (!marker.hasPhoto() || !photoOriginalUri.equals(marker.getPhotoURI()))) {
deletePhoto(photoOriginalUri);
}
}
public void onDone(String name, String category, String description) {
Marker marker = getMarker();
if (isNewMarker) {
onAddDone(marker, name, category, description);
} else {
onSaveDone(marker, name, category, description);
}
}
public void onCancel() {
Marker marker = getMarker();
if (isNewMarker) {
// it's new marker -> clean all photos.
deletePhoto(marker);
deletePhoto(photoOriginalUri);
} else if (photoOriginalUri == null || (marker.hasPhoto() && !marker.getPhotoURI().equals(photoOriginalUri))) {
// it's an edit marker -> delete photo if it was empty or it was changed (leaving the original in that case).
deletePhoto(marker);
}
}
}
@@ -0,0 +1,345 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.ui.markers;
import android.app.SearchManager;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.ContextualActionModeCallback;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.adapters.ScrollVisibleViews;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.databinding.MarkerListBinding;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.ui.markers.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentUtils;
/**
* Activity to show a list of markers in a track.
*
* @author Leif Hendrik Wilden
*/
public class MarkerListActivity extends AbstractActivity implements DeleteMarkerCaller {
public static final String EXTRA_TRACK_ID = "track_id";
private static final String TAG = MarkerListActivity.class.getSimpleName();
private ContentProviderUtils contentProviderUtils;
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private Track track;
private MarkerResourceCursorAdapter resourceCursorAdapter;
private MarkerListBinding viewBinding;
private final MarkerLoaderCallback loaderCallbacks = new MarkerLoaderCallback();
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private final TrackRecordingServiceConnection.Callback bindCallback = service -> service.getRecordingStatusObservable()
.observe(MarkerListActivity.this, this::onRecordingStatusChanged);
// Callback when an item is selected in the contextual action mode
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@Override
public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
boolean isSingleSelection = ids.length == 1;
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection);
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
menu.findItem(R.id.list_context_menu_delete).setVisible(true);
// Set select all to the same visibility as delete since delete is the only action that can be applied to multiple markers.
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
}
@Override
public boolean onClick(int itemId, int[] positions, long[] ids) {
return handleContextItem(itemId, ids);
}
};
private MenuItem insertMarkerMenuItem;
private MenuItem searchMenuItem;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
contentProviderUtils = new ContentProviderUtils(this);
track = trackId != null ? contentProviderUtils.getTrack(trackId) : null;
viewBinding.markerList.setEmptyView(viewBinding.markerListEmpty);
viewBinding.markerList.setOnItemClickListener((parent, view, position, id) -> {
resourceCursorAdapter.markerInvalid(id);
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, new Marker.Id(id));
startActivity(intent);
});
resourceCursorAdapter = new MarkerResourceCursorAdapter(this, R.layout.list_item);
ScrollVisibleViews scrollVisibleViews = new ScrollVisibleViews(resourceCursorAdapter);
viewBinding.markerList.setOnScrollListener(scrollVisibleViews);
viewBinding.markerList.setAdapter(resourceCursorAdapter);
ActivityUtils.configureListViewContextualMenu(viewBinding.markerList, contextualActionModeCallback);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
}
@Override
protected void onStart() {
super.onStart();
trackRecordingServiceConnection.startConnection(this);
}
@Override
protected void onResume() {
super.onResume();
trackRecordingServiceConnection.bind(this);
this.invalidateOptionsMenu();
loadData(getIntent());
}
@Override
protected void onStop() {
super.onStop();
trackRecordingServiceConnection.unbind(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
resourceCursorAdapter.clear();
viewBinding = null;
resourceCursorAdapter = null;
contentProviderUtils = null;
}
@Override
protected View getRootView() {
viewBinding = MarkerListBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.marker_list, menu);
insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker);
searchMenuItem = menu.findItem(R.id.marker_list_search);
ActivityUtils.configureSearchWidget(this, searchMenuItem);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
insertMarkerMenuItem.setVisible(track != null && track.getId().equals(recordingStatus.getTrackId()) && !recordingStatus.isPaused());
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (track != null && item.getItemId() == R.id.marker_list_insert_marker) {
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, track.getId());
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Handles a context item selection.
*
* @param itemId the menu item id
* @param longMarkerIds the marker ids
* @return true if handled.
*/
private boolean handleContextItem(int itemId, long... longMarkerIds) {
Marker.Id[] markerIds = new Marker.Id[longMarkerIds.length];
for (int i = 0; i < longMarkerIds.length; i++) {
markerIds[i] = new Marker.Id(longMarkerIds[i]);
}
if (itemId == R.id.list_context_menu_show_on_map) {
if (markerIds.length == 1) {
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getMarker(markerIds[0]));
}
return true;
}
if (itemId == R.id.list_context_menu_share) {
Intent intent = IntentUtils.newShareFileIntent(this, markerIds);
if (intent != null) {
intent = Intent.createChooser(intent, null);
startActivity(intent);
}
return true;
}
if (itemId == R.id.list_context_menu_edit) {
if (markerIds.length == 1) {
resourceCursorAdapter.markerInvalid(markerIds[0].getId());
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]);
startActivity(intent);
}
return true;
}
if (itemId == R.id.list_context_menu_delete) {
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
return true;
}
if (itemId == R.id.list_context_menu_select_all) {
for (int i = 0; i < viewBinding.markerList.getCount(); i++) {
viewBinding.markerList.setItemChecked(i, true);
}
return false;
}
return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) {
return true;
}
return super.onKeyUp(keyCode, event);
}
@Override
public void onBackPressed() {
SearchView searchView = (SearchView) searchMenuItem.getActionView();
if (!searchView.isIconified()) {
searchView.setIconified(true);
}
if (loaderCallbacks.getSearchQuery() != null) {
loaderCallbacks.setSearch(null);
return;
}
super.onBackPressed();
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
loadData(intent);
}
private void loadData(Intent intent) {
String searchQuery = null;
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
searchQuery = intent.getStringExtra(SearchManager.QUERY);
}
loaderCallbacks.setSearch(searchQuery);
}
@Override
public void onMarkerDeleted() {
// Do nothing
}
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
recordingStatus = status;
}
private class MarkerLoaderCallback implements LoaderManager.LoaderCallbacks<Cursor> {
private String searchQuery = null;
public String getSearchQuery() {
return searchQuery;
}
public void setSearch(String searchQuery) {
this.searchQuery = searchQuery;
restart();
if (searchQuery != null) {
setTitle(searchQuery);
} else {
setTitle(R.string.menu_markers);
}
}
public void restart() {
LoaderManager.getInstance(MarkerListActivity.this).restartLoader(0, null, loaderCallbacks);
}
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
final String[] PROJECTION = new String[]{MarkerColumns._ID,
MarkerColumns.NAME, MarkerColumns.DESCRIPTION, MarkerColumns.CATEGORY,
MarkerColumns.TIME, MarkerColumns.PHOTOURL, MarkerColumns.TRACKID};
if (searchQuery == null) {
if (track != null) {
return new CursorLoader(MarkerListActivity.this, MarkerColumns.CONTENT_URI, PROJECTION, MarkerColumns.TRACKID + "=?", new String[]{String.valueOf(track.getId().getId())}, null);
} else {
return new CursorLoader(MarkerListActivity.this, MarkerColumns.CONTENT_URI, PROJECTION, null, null, null);
}
} else {
final String SEARCH_QUERY = MarkerColumns.NAME + " LIKE ? OR " +
MarkerColumns.DESCRIPTION + " LIKE ? OR " +
MarkerColumns.CATEGORY + " LIKE ?";
final String[] selectionArgs = new String[]{"%" + searchQuery + "%", "%" + searchQuery + "%", "%" + searchQuery + "%"};
return new CursorLoader(MarkerListActivity.this, MarkerColumns.CONTENT_URI, PROJECTION, SEARCH_QUERY, selectionArgs, MarkerColumns.DEFAULT_SORT_ORDER + " DESC");
}
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
resourceCursorAdapter.swapCursor(cursor);
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
resourceCursorAdapter.swapCursor(null);
}
}
}
@@ -0,0 +1,195 @@
package de.dennisguse.opentracks.ui.markers;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.collection.LruCache;
import androidx.cursoradapter.widget.ResourceCursorAdapter;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
import java.time.OffsetDateTime;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.adapters.ExecutorListViewService;
import de.dennisguse.opentracks.adapters.ScrollVisibleViews;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implements ScrollVisibleViews.VisibleViewsListener {
private static final String TAG = MarkerResourceCursorAdapter.class.getSimpleName();
private static final int LIST_PREFERRED_ITEM_HEIGHT_DEFAULT = 128;
private final Activity activity;
//TODO Should be Marker.Id
private final ExecutorListViewService<Long> executorService = new ExecutorListViewService<>(LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
private boolean scroll = false;
// Cache size is in bytes.
private final LruCache<String, Bitmap> memoryCache;
public MarkerResourceCursorAdapter(Activity activity, int layout) {
super(activity, layout, null, 0);
this.activity = activity;
// Get max available VM memory, exceeding this amount will throw an OutOfMemory exception.
final long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
final int cacheSize = (int) (Runtime.getRuntime().maxMemory() - usedMemory) / 8;
memoryCache = new LruCache<>(cacheSize) {
@Override
protected int sizeOf(@NonNull String key, @NonNull Bitmap bitmap) {
return bitmap.getByteCount();
}
};
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
int idIndex = cursor.getColumnIndex(MarkerColumns._ID);
int nameIndex = cursor.getColumnIndex(MarkerColumns.NAME);
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
int categoryIndex = cursor.getColumnIndex(MarkerColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(MarkerColumns.DESCRIPTION);
int photoUrlIndex = cursor.getColumnIndex(MarkerColumns.PHOTOURL);
int trackIdIndex = cursor.getColumnIndex(MarkerColumns.TRACKID);
long id = cursor.getLong(idIndex);
int iconId = MarkerUtils.ICON_ID;
String name = cursor.getString(nameIndex);
long time = cursor.getLong(timeIndex);
String category = cursor.getString(categoryIndex);
String description = cursor.getString(descriptionIndex);
String photoUrl = cursor.getString(photoUrlIndex);
long trackId = cursor.getLong(trackIdIndex);
view.setTag(String.valueOf(id));
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
ImageView imageView = view.findViewById(R.id.list_item_photo);
ImageView textGradient = view.findViewById(R.id.list_item_text_gradient);
imageView.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
imageView.setImageBitmap(null);
if (hasPhoto) {
int height = getPhotoHeight(activity);
ViewGroup.LayoutParams params = imageView.getLayoutParams();
params.height = height;
imageView.setLayoutParams(params);
if (getBitmapFromMemCache(String.valueOf(id)) != null || !scroll) {
asyncLoadPhoto(view, imageView, photoUrl, id);
}
}
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
Track track = contentProviderUtils.getTrack(new Track.Id(trackId));
ListItemUtils.setListItem(activity, view, false, true, iconId, R.string.image_marker, name, null, null, 0,
OffsetDateTime.ofInstant(Instant.ofEpochMilli(time), track.getZoneOffset()), category, description, hasPhoto);
}
public void clear() {
executorService.shutdown();
}
@Override
public void onViewVisible(View view, int position) {
scroll = true;
Cursor cursor = getCursor();
if (!cursor.moveToPosition(position)) {
return;
}
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MarkerColumns._ID));
String photoUrl = cursor.getString(cursor.getColumnIndexOrThrow(MarkerColumns.PHOTOURL));
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
if (hasPhoto) {
ImageView imageView = view.findViewById(R.id.list_item_photo);
asyncLoadPhoto(view, imageView, photoUrl, id);
}
}
public void markerInvalid(long id) {
memoryCache.remove(String.valueOf(id));
scroll = false;
}
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
synchronized (memoryCache) {
if (getBitmapFromMemCache(key) == null) {
memoryCache.put(key, bitmap);
}
}
}
private Bitmap getBitmapFromMemCache(String key) {
return memoryCache.get(key);
}
/**
* It loads the photoUrl in the imageView from view.
* It takes the photo from cache or from storage if isn't in the cache.
*
* @param view item's view.
* @param imageView view object where photo will be loaded.
* @param photoUrl photo's url.
* @param id marker's id where photo belong.
*/
private void asyncLoadPhoto(View view, ImageView imageView, String photoUrl, long id) {
Bitmap photo = getBitmapFromMemCache(String.valueOf(id));
imageView.setImageBitmap(photo);
if (photo == null) {
executorService.execute(id, () -> {
try (InputStream inputStream = activity.getContentResolver().openInputStream(Uri.parse(photoUrl))) {
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
int height = getPhotoHeight(activity);
Log.d(TAG, "Width : " + (bitmap.getWidth() / (bitmap.getHeight() / height)) + " | Height: " + height);
final Bitmap finalPhoto = ThumbnailUtils.extractThumbnail(bitmap, bitmap.getWidth() / (bitmap.getHeight() / height), height);
addBitmapToMemoryCache(String.valueOf(id), finalPhoto);
if (view.getTag().equals(String.valueOf(id))) {
activity.runOnUiThread(() -> imageView.setImageBitmap(finalPhoto));
}
} catch (IOException e) {
Log.e(TAG, "Failed to image " + e);
}
});
}
}
/**
* Gets the photo height.
*
* @param context the context
*/
private static int getPhotoHeight(Context context) {
int[] attrs = new int[]{android.R.attr.listPreferredItemHeight};
TypedArray typeArray = context.obtainStyledAttributes(attrs);
int height = typeArray.getDimensionPixelSize(0, LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
typeArray.recycle();
return 2 * height;
}
}
@@ -0,0 +1,21 @@
package de.dennisguse.opentracks.ui.markers;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import de.dennisguse.opentracks.R;
public class MarkerUtils {
public static final int ICON_ID = R.drawable.ic_marker_orange_pushpin_with_shadow;
private MarkerUtils() {
}
public static Drawable getDefaultPhoto(@NonNull Context context) {
return ContextCompat.getDrawable(context, ICON_ID);
}
}