diff --git a/app/build.gradle b/app/build.gradle index 858d293..e36b07e 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,8 +21,8 @@ android { applicationId "fr.nuage.souvenirs" minSdkVersion 24 targetSdkVersion 30 - versionCode 20 - versionName "2.5.0" + versionCode 21 + versionName "2.6.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" setProperty("archivesBaseName", "souvenirs-$versionName") } diff --git a/app/src/main/java/fr/nuage/souvenirs/SettingsActivity.java b/app/src/main/java/fr/nuage/souvenirs/SettingsActivity.java index e63cfb1..036bc05 100755 --- a/app/src/main/java/fr/nuage/souvenirs/SettingsActivity.java +++ b/app/src/main/java/fr/nuage/souvenirs/SettingsActivity.java @@ -87,11 +87,17 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { } - //display version findPreference(NEXTCLOUD_VERSION).setSummary(BuildConfig.VERSION_NAME); } + @Override + public void onPause() { + //refresh NCenabled status in case wifi setting change + NCUtils.updateNCState(); + super.onPause(); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); diff --git a/app/src/main/java/fr/nuage/souvenirs/model/Album.java b/app/src/main/java/fr/nuage/souvenirs/model/Album.java index 76dcfe7..4d45bc3 100755 --- a/app/src/main/java/fr/nuage/souvenirs/model/Album.java +++ b/app/src/main/java/fr/nuage/souvenirs/model/Album.java @@ -36,9 +36,9 @@ public class Album { public static final String STYLE_TILE = "TILE"; private String albumPath; - private MutableLiveData ldName = new MutableLiveData(); + private MutableLiveData ldName = new MutableLiveData<>(); private String name; - private MutableLiveData> ldPages = new MutableLiveData>(); + private MutableLiveData> ldPages = new MutableLiveData<>(); private ArrayList pages = new ArrayList(); private Date pagesLastEditDate; private MutableLiveData ldPagesLastEditDate = new MutableLiveData<>(); @@ -128,11 +128,6 @@ public String getDefaultStyle() { return defaultStyle; } - public void reload() { - if (!unsavedModifications) { - load(); - } - } public boolean load() { Log.d(this.getClass().toString(),"Load Album "+albumPath); @@ -229,7 +224,7 @@ public boolean load() { } } } catch (JSONException e) { - Log.w(this.getClass().getSimpleName(),"Wrong file format for "+this.albumPath); + Log.w(this.getClass().getSimpleName(),"Wrong file format for "+this.albumPath,e); return false; } updateAllLiveDataObject(); diff --git a/app/src/main/java/fr/nuage/souvenirs/model/Albums.java b/app/src/main/java/fr/nuage/souvenirs/model/Albums.java index f5b12b0..2c859b2 100755 --- a/app/src/main/java/fr/nuage/souvenirs/model/Albums.java +++ b/app/src/main/java/fr/nuage/souvenirs/model/Albums.java @@ -69,8 +69,6 @@ public void updateAlbumList() { Album album = getAlbum(f.getPath()); if (album == null) { albumList.add(new Album(f.getPath())); - } else { - album.reload(); } } } diff --git a/app/src/main/java/fr/nuage/souvenirs/model/AudioElement.java b/app/src/main/java/fr/nuage/souvenirs/model/AudioElement.java new file mode 100644 index 0000000..f16ef52 --- /dev/null +++ b/app/src/main/java/fr/nuage/souvenirs/model/AudioElement.java @@ -0,0 +1,85 @@ +package fr.nuage.souvenirs.model; + +import androidx.lifecycle.MutableLiveData; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.InputStream; + +public class AudioElement extends Element { + + private String audioPath; + private boolean stop; + private MutableLiveData ldAudioPath = new MutableLiveData<>(); + private MutableLiveData ldStop = new MutableLiveData<>(); + + public AudioElement(String audioPath, boolean stop) { + super(); + setAudioPath(audioPath,false); + setStop(stop,false); + } + + public AudioElement() { + this("",false); + } + + @Override + public JSONObject completeToJSON(JSONObject json) throws JSONException { + json.put("audio",Utils.getRelativePath(pageParent.getAlbum().getAlbumPath(),audioPath)); + json.put("stop",stop); + return json; + } + + @Override + public void completeFromJSON(JSONObject jsonObject) throws JSONException { + if (jsonObject.has("audio")) { + setAudioPath(new File(pageParent.getAlbum().getAlbumPath(),jsonObject.getString("audio")).getPath(),false); + } + if (jsonObject.has("stop")) { + setStop(jsonObject.getBoolean("stop"),false); + } + } + + public void setAudioPath(String audioPath, boolean save) { + this.audioPath = audioPath; + ldAudioPath.postValue(audioPath); + if (save) { + onChange(); + } + } + + public void setStop(boolean stop, boolean save) { + this.stop = stop; + ldStop.postValue(stop); + if (save) { + onChange(); + } + } + + public String getAudioPath() { + return audioPath; + } + + public boolean isStop() { + return stop; + } + + public MutableLiveData getLdAudioPath() { + return ldAudioPath; + } + + public MutableLiveData getLdStop() { + return ldStop; + } + + public void setAudio(InputStream input, String mimeType) { + String audioPath = pageParent.getAlbum().createDataFile(input,mimeType); + if (audioPath != null) { + setAudioPath(audioPath,true); + } else { + setAudioPath("",true); + } + } +} diff --git a/app/src/main/java/fr/nuage/souvenirs/model/ImageElement.java b/app/src/main/java/fr/nuage/souvenirs/model/ImageElement.java index bb5db32..48f63f5 100644 --- a/app/src/main/java/fr/nuage/souvenirs/model/ImageElement.java +++ b/app/src/main/java/fr/nuage/souvenirs/model/ImageElement.java @@ -1,7 +1,9 @@ package fr.nuage.souvenirs.model; +import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.ExifInterface; +import android.util.Log; import androidx.lifecycle.MutableLiveData; @@ -10,6 +12,7 @@ import java.io.BufferedReader; import java.io.File; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; @@ -21,12 +24,12 @@ public class ImageElement extends Element { public static final int ZOOM_OFFSET = 2; public static final String GOOGLE_PANORAMA_360_MIMETYPE = "application/vnd.google.panorama360+jpg"; - private MutableLiveData ldImagePath = new MutableLiveData(); - private MutableLiveData ldTransformType = new MutableLiveData<>(); - private MutableLiveData ldZoom = new MutableLiveData<>(); - private MutableLiveData ldOffsetX = new MutableLiveData<>(); - private MutableLiveData ldOffsetY = new MutableLiveData<>(); - private MutableLiveData ldIsPano = new MutableLiveData<>(); + private final MutableLiveData ldImagePath = new MutableLiveData(); + private final MutableLiveData ldTransformType = new MutableLiveData<>(); + private final MutableLiveData ldZoom = new MutableLiveData<>(); + private final MutableLiveData ldOffsetX = new MutableLiveData<>(); + private final MutableLiveData ldOffsetY = new MutableLiveData<>(); + private final MutableLiveData ldIsPano = new MutableLiveData<>(); private String imagePath; private String mimeType; private String name=""; @@ -154,6 +157,15 @@ public void setImage(InputStream input, String mimeType) { } } + public void setImage(Bitmap bitmap) { + File imFile = pageParent.getAlbum().createEmptyDataFile("image/jpeg"); + try (FileOutputStream out = new FileOutputStream(imFile)) { + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out); + } catch (IOException e) { + Log.e(getClass().getName(),"Impossible to save image.",e); + } + setImage(imFile.getPath(),"image/jpeg"); + } /** * This method set the image path attribute to element, without deleting the previous one. diff --git a/app/src/main/java/fr/nuage/souvenirs/model/Page.java b/app/src/main/java/fr/nuage/souvenirs/model/Page.java index 573289f..93f3eab 100755 --- a/app/src/main/java/fr/nuage/souvenirs/model/Page.java +++ b/app/src/main/java/fr/nuage/souvenirs/model/Page.java @@ -18,7 +18,7 @@ public class Page { - private MutableLiveData> ldElementsList = new MutableLiveData>(); + private final MutableLiveData> ldElementsList = new MutableLiveData>(); private ArrayList elementsList; private Album albumParent; private UUID id; @@ -210,7 +210,7 @@ public int getNbTxt() { public int getNbImage() { int nb=0; for (Element e: getElements()) { - if (e.getClass().equals(ImageElement.class)) { + if (e instanceof ImageElement) { nb += 1; } } @@ -277,4 +277,25 @@ public PaintElement createPaintElement() { addElement(paintElement); return paintElement; } + + public AudioElement createAudioElement() { + AudioElement audioElement = new AudioElement(); + addElement(audioElement); + return audioElement; + } + + public void removeAudio() { + for (Element e : getElements()) { + if (e.getClass().equals(AudioElement.class)) { + delElement(e); + return; + } + } + } + + public VideoElement createVideoElement() { + VideoElement videoElement = new VideoElement(); + addElement(videoElement); + return videoElement; + } } diff --git a/app/src/main/java/fr/nuage/souvenirs/model/PageBuilder.java b/app/src/main/java/fr/nuage/souvenirs/model/PageBuilder.java index ff4d32b..ac816bf 100755 --- a/app/src/main/java/fr/nuage/souvenirs/model/PageBuilder.java +++ b/app/src/main/java/fr/nuage/souvenirs/model/PageBuilder.java @@ -231,7 +231,7 @@ public static InputStream getInputStreamFromUri(ContentResolver cr, Uri uri) { } public void create(int style, AlbumViewModel albumVM, ArrayList images, ArrayList texts) { - create(style,albumVM,albumVM.getPages().getValue().size(),images,texts); + create(style,albumVM,albumVM.getLdPages().getValue().size(),images,texts); } /* create one page and import all image/text on the page, take default style @@ -298,14 +298,15 @@ public void create(int style, AlbumViewModel albumVM, int position, ArrayList texts = new ArrayList(); - ArrayList images = new ArrayList(); + ArrayList texts = new ArrayList<>(); + ArrayList images = new ArrayList<>(); //parse page and fill text and image arrays for (Element e : page.getElements()) { if (e.getClass().equals(TextElement.class)) { texts.add(((TextElement)e).getText()); } - if (e.getClass().equals(ImageElement.class)) { + if (e instanceof ImageElement) { + //FIXME : will not work with video images.add(Uri.fromFile(new File(((ImageElement)e).getImagePath()))); } } diff --git a/app/src/main/java/fr/nuage/souvenirs/model/TilePageBuilder.java b/app/src/main/java/fr/nuage/souvenirs/model/TilePageBuilder.java index d43e3de..6f13503 100755 --- a/app/src/main/java/fr/nuage/souvenirs/model/TilePageBuilder.java +++ b/app/src/main/java/fr/nuage/souvenirs/model/TilePageBuilder.java @@ -160,14 +160,14 @@ public void create(int style, AlbumViewModel albumVM, int position, ArrayList imageElementArrayList = new ArrayList<>(); + ArrayList imageElementArrayList = new ArrayList<>(); ArrayList textElementArrayList = new ArrayList<>(); for (Element e : page.getElements()) { if (e.getClass().equals(TextElement.class)) { textElementArrayList.add((TextElement)e); } - if (e.getClass().equals(ImageElement.class)) { - imageElementArrayList.add((ImageElement)e); + if (e instanceof ImageElement) { + imageElementArrayList.add(e); } } //read style template and apply @@ -176,7 +176,7 @@ public void applyStyle(int style, Page page) { while ( (imCursor < imageElementArrayList.size()) || (txtCursor < textElementArrayList.size()) ) { for (Object[] elDef: getPageStyleMap()[style]) { if (imCursor < imageElementArrayList.size()) { - ImageElement e_img = imageElementArrayList.get(imCursor); + ImageElement e_img = (ImageElement) imageElementArrayList.get(imCursor); if (e_img != null) { e_img.setTop((int)elDef[1]); e_img.setBottom((int)elDef[3]); diff --git a/app/src/main/java/fr/nuage/souvenirs/model/VideoElement.java b/app/src/main/java/fr/nuage/souvenirs/model/VideoElement.java new file mode 100644 index 0000000..685cd62 --- /dev/null +++ b/app/src/main/java/fr/nuage/souvenirs/model/VideoElement.java @@ -0,0 +1,138 @@ +package fr.nuage.souvenirs.model; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.ExifInterface; +import android.media.MediaMetadataRetriever; +import android.media.ThumbnailUtils; +import android.provider.MediaStore; +import android.util.Size; + +import androidx.lifecycle.MutableLiveData; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; + +public class VideoElement extends ImageElement { + + private final MutableLiveData ldVideoPath = new MutableLiveData<>(); + private String videoPath; + + public VideoElement() { + this(""); + } + + public VideoElement(int left, int top, int right, int bottom) { + this("",left,top,right,bottom); + } + + public VideoElement(String imgPath) { + this(imgPath,0,0,100,100); + } + + public VideoElement(String imgPath, int left, int top, int right, int bottom) { + this(imgPath,left,top,right,bottom,""); + } + + public VideoElement(String imgPath, int left, int top, int right, int bottom, String mimeType) { + this(imgPath,left,top,right,bottom,mimeType,""); + } + + public VideoElement(String imgPath, int left, int top, int right, int bottom, String mimeType, String videoPath) { + super(imgPath,left, top, right, bottom); + this.videoPath = videoPath; + ldVideoPath.postValue(videoPath); + } + + public void setVideoPath(String path) { + setVideoPath(path,true); + } + + public void setVideoPath(String path, boolean save) { + videoPath = path; + ldVideoPath.postValue(videoPath); + if (save) { + onChange(); + } + } + + public void setVideo(InputStream input, String mimeType) { + String videoPath = pageParent.getAlbum().createDataFile(input,mimeType); + if (videoPath != null) { + setVideoPath(videoPath); + setMimeType(mimeType); + } else { + setVideoPath(""); + setMimeType(""); + } + //create image from thumbmail + Bitmap thumb = ThumbnailUtils.createVideoThumbnail(getVideoPath(), MediaStore.Images.Thumbnails.FULL_SCREEN_KIND); + setImage(thumb); + } + + private static Size getThumbSize(String path) { + MediaMetadataRetriever dataRetriever = new MediaMetadataRetriever(); + dataRetriever.setDataSource(path); + String width = + dataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); + String height = + dataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); + return new Size(Integer.parseInt(width), Integer.parseInt(height)); + } + + + + /** + * This method set the image path attribute to element, without deleting the previous one. + * Path must be local to album, ie already set in album data path + */ + public void setVideo(String localVideoPath, String mimeType) { + setImagePath(localVideoPath); + setMimeType(mimeType); + } + + @Override + public JSONObject completeToJSON(JSONObject json) throws JSONException { + json = super.completeToJSON(json); + json.put("video",Utils.getRelativePath(pageParent.getAlbum().getAlbumPath(),videoPath)); + return json; + } + + @Override + public void completeFromJSON(JSONObject jsonObject) throws JSONException { + super.completeFromJSON(jsonObject); + //must be called from UI thread + if (jsonObject.has("video")) { + setVideoPath(new File(pageParent.getAlbum().getAlbumPath(),jsonObject.getString("video")).getPath(),false); + } + } + + public String getVideoPath() { + return videoPath; + } + + private void deleteVideoFile() { + if (getVideoPath() != null ) { + File videoFile = new File(getVideoPath()); + if (videoFile.exists()) { + videoFile.delete(); + } + } + } + + @Override + public void delete() { + deleteVideoFile(); + super.delete(); + } + + public MutableLiveData getLdVideoPath() { + return ldVideoPath; + } +} diff --git a/app/src/main/java/fr/nuage/souvenirs/model/nc/APIProvider.java b/app/src/main/java/fr/nuage/souvenirs/model/nc/APIProvider.java index e08c487..4fdc551 100644 --- a/app/src/main/java/fr/nuage/souvenirs/model/nc/APIProvider.java +++ b/app/src/main/java/fr/nuage/souvenirs/model/nc/APIProvider.java @@ -118,6 +118,13 @@ public static class ElementResp { int offsetY; //for textelement String text; + //for audio element + @SerializedName("audio") + String audioPath; + boolean stop; + //for video element + @SerializedName("video") + public String videoPath; } } diff --git a/app/src/main/java/fr/nuage/souvenirs/model/nc/AudioElementNC.java b/app/src/main/java/fr/nuage/souvenirs/model/nc/AudioElementNC.java new file mode 100644 index 0000000..c143d02 --- /dev/null +++ b/app/src/main/java/fr/nuage/souvenirs/model/nc/AudioElementNC.java @@ -0,0 +1,69 @@ +package fr.nuage.souvenirs.model.nc; + +import org.json.JSONException; +import org.json.JSONObject; + +public class AudioElementNC extends ElementNC { + + private String audioPath; + private boolean stop; + + public AudioElementNC() { + this("",false); + } + + public AudioElementNC(String audioPath, boolean stop) { + super(); + this.audioPath = audioPath; + this.stop = stop; + } + + @Override + public JSONObject completeToJSON(JSONObject json) throws JSONException { + return null; + } + + @Override + public void completeFromJSON(JSONObject jsonObject) throws JSONException { + if (jsonObject.has("audio")) { + String imagePath = jsonObject.getString("audio"); + setAudioPath(imagePath); + } + if (jsonObject.has("stop")) { + setStop(jsonObject.getBoolean("stop")); + } + } + + public void setAudioPath(String audioPath) { + this.audioPath = audioPath; + onChange(); + } + + public void setStop(boolean stop) { + this.stop = stop; + onChange(); + } + + public String getAudioPath() { + return audioPath; + } + + public boolean isStop() { + return stop; + } + + @Override + public void load(APIProvider.ElementResp elementResp) { + super.load(elementResp); + setAudioPath(elementResp.audioPath); + setStop(elementResp.stop); + } + + @Override + public APIProvider.ElementResp generateElementResp() { + APIProvider.ElementResp elementResp = super.generateElementResp(); + elementResp.audioPath = getAudioPath(); + elementResp.stop = isStop(); + return elementResp; + } +} diff --git a/app/src/main/java/fr/nuage/souvenirs/model/nc/PageNC.java b/app/src/main/java/fr/nuage/souvenirs/model/nc/PageNC.java index d4a7aee..34cf158 100755 --- a/app/src/main/java/fr/nuage/souvenirs/model/nc/PageNC.java +++ b/app/src/main/java/fr/nuage/souvenirs/model/nc/PageNC.java @@ -175,12 +175,25 @@ public boolean pushAssets(String localAlbumPath, AlbumNC albumNC) { if (!albumNC.pushAsset(localAlbumPath,ime.getImagePath())) { return false; } + } else if (e instanceof AudioElementNC) { //push audio + AudioElementNC aue = (AudioElementNC) e; + if (!aue.getAudioPath().equals("")) { + if (!albumNC.pushAsset(localAlbumPath, aue.getAudioPath())) { + return false; + } + } + } + if (e instanceof VideoElementNC) { + VideoElementNC ve = (VideoElementNC) e; + if (!albumNC.pushAsset(localAlbumPath,ve.getVideoPath())) { + return false; + } } } return true; } - public boolean pullAssets(String localAlbumPath, AlbumNC albumNC) { + public boolean pullAssets(String localAlbumPath, AlbumNC albumNC) { //pull images for (ElementNC e : getElements()) { if (e instanceof ImageElementNC) { @@ -188,6 +201,19 @@ public boolean pullAssets(String localAlbumPath, AlbumNC albumNC) { if (!albumNC.pullAsset(localAlbumPath,ime.getImagePath())) { return false; } + } else if (e instanceof AudioElementNC) { + AudioElementNC aue = (AudioElementNC) e; + if (!aue.getAudioPath().equals("")) { + if (!albumNC.pullAsset(localAlbumPath, aue.getAudioPath())) { + return false; + } + } + } + if (e instanceof VideoElementNC) { + VideoElementNC ve = (VideoElementNC) e; + if (!albumNC.pullAsset(localAlbumPath,ve.getVideoPath())) { + return false; + } } } return true; diff --git a/app/src/main/java/fr/nuage/souvenirs/model/nc/VideoElementNC.java b/app/src/main/java/fr/nuage/souvenirs/model/nc/VideoElementNC.java new file mode 100644 index 0000000..95385e4 --- /dev/null +++ b/app/src/main/java/fr/nuage/souvenirs/model/nc/VideoElementNC.java @@ -0,0 +1,77 @@ +package fr.nuage.souvenirs.model.nc; + +import org.json.JSONException; +import org.json.JSONObject; + +public class VideoElementNC extends ImageElementNC { + + + private String videoPath; + + public VideoElementNC() { + this(""); + } + + public VideoElementNC(int left, int top, int right, int bottom) { + this("",left,top,right,bottom); + } + + public VideoElementNC(String imgPath) { + this(imgPath,0,0,100,100); + } + + public VideoElementNC(String imgPath, int left, int top, int right, int bottom) { + this(imgPath,left,top,right,bottom,""); + } + + public VideoElementNC(String imgPath, int left, int top, int right, int bottom, String mimeType) { + this(imgPath,left,top,right,bottom,mimeType,""); + } + + public VideoElementNC(String imgPath, int left, int top, int right, int bottom, String mimeType, String videoPath) { + super(imgPath,left, top, right, bottom,mimeType); + this.videoPath = videoPath; + } + + public void setVideoPath(String path) { + this.videoPath = path; + onChange(); + } + + + @Override + public JSONObject completeToJSON(JSONObject json) throws JSONException { + json = super.completeToJSON(json); + json.put("video",videoPath); + return json; + } + + @Override + public void completeFromJSON(JSONObject jsonObject) throws JSONException { + //must be called from UI thread + if (jsonObject.has("video")) { + String path = jsonObject.getString("video"); + setVideoPath(path); + } + super.completeFromJSON(jsonObject); + } + + @Override + public void load(APIProvider.ElementResp elementResp) { + super.load(elementResp); + setVideoPath(elementResp.videoPath); + } + + @Override + public APIProvider.ElementResp generateElementResp() { + APIProvider.ElementResp resp = super.generateElementResp(); + resp.videoPath = getVideoPath(); + return resp; + } + + public String getVideoPath() { + return videoPath; + } + + +} diff --git a/app/src/main/java/fr/nuage/souvenirs/view/DataBindingAdapters.java b/app/src/main/java/fr/nuage/souvenirs/view/DataBindingAdapters.java index 3dfc573..9f5286c 100644 --- a/app/src/main/java/fr/nuage/souvenirs/view/DataBindingAdapters.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/DataBindingAdapters.java @@ -6,6 +6,7 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.VideoView; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.Guideline; diff --git a/app/src/main/java/fr/nuage/souvenirs/view/EditAlbumFragment.java b/app/src/main/java/fr/nuage/souvenirs/view/EditAlbumFragment.java index 5312210..d77efb7 100644 --- a/app/src/main/java/fr/nuage/souvenirs/view/EditAlbumFragment.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/EditAlbumFragment.java @@ -13,8 +13,6 @@ import android.widget.PopupMenu; import androidx.annotation.NonNull; -import androidx.appcompat.view.menu.MenuBuilder; -import androidx.appcompat.view.menu.MenuPopupHelper; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; @@ -94,7 +92,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, AlbumViewModel albumVM = albumListViewModel.getAlbum(albumPath); if (albumVM != null) { setAlbumVM(albumVM); - albumVM.getPages().observe(lifecycleOwner, PageViewModels -> editPageListAdapter.updateList(PageViewModels)); + albumVM.getLdPages().observe(lifecycleOwner, PageViewModels -> editPageListAdapter.updateList(PageViewModels)); getActivity().setTitle(albumVM.getName().getValue()); albumVM.getName().observe(lifecycleOwner, s -> getActivity().setTitle(albumVM.getName().getValue())); if (initialPageFocusId != null) { diff --git a/app/src/main/java/fr/nuage/souvenirs/view/EditPageFragment.java b/app/src/main/java/fr/nuage/souvenirs/view/EditPageFragment.java index d378c57..b1d2085 100644 --- a/app/src/main/java/fr/nuage/souvenirs/view/EditPageFragment.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/EditPageFragment.java @@ -57,6 +57,7 @@ public class EditPageFragment extends Fragment { private static final int ACTIVITY_ADD_IMAGE = 10; private static final int ACTIVITY_ADD_PHOTO = 11; + private static final int ACTIVITY_ADD_AUDIO = 12; private static final String DIALOG_CHANGE_STYLE_PAGE = "DIALOG_CHANGE_STYLE_PAGE"; @@ -113,7 +114,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, } }); //listen to pages changes - albumVM.getPages().observe(getViewLifecycleOwner(), pageViewModels -> { + albumVM.getLdPages().observe(getViewLifecycleOwner(), pageViewModels -> { //build new view ((ViewGroup) requireView()).removeAllViews(); ((ViewGroup) requireView()).addView(createView(getLayoutInflater(), (ViewGroup) getView())); @@ -126,21 +127,29 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, private View createView(@NonNull LayoutInflater inflater, ViewGroup container) { //inflateview FragmentEditPageBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_edit_page, container, false); + binding.setLifecycleOwner(getViewLifecycleOwner()); + binding.setPage(pageVM); + binding.executePendingBindings(); pageVM.getLdEditMode().postValue(true); binding.pageViewEdit.setPageViewModel(pageVM); + //listen for audiomode change to change menu + pageVM.getLdAudioMode().observe(getViewLifecycleOwner(), audioMode -> { + getActivity().invalidateOptionsMenu(); + }); + binding.mainLayout.setOnClickListener(view -> { //if we recieve click, means no element has catch it : off page click, unselect all - if (pageVM.getElements().getValue() != null) { - for (ElementViewModel e : pageVM.getElements().getValue()) { + if (pageVM.getLdElements().getValue() != null) { + for (ElementViewModel e : pageVM.getLdElements().getValue()) { e.setSelected(false); } } }); //listen to elements changes - pageVM.getElements().observe(getViewLifecycleOwner(), elementViewModels -> { + pageVM.getLdElements().observe(getViewLifecycleOwner(), elementViewModels -> { //set observers if (elementViewModels != null) { for (ElementViewModel e : elementViewModels) { @@ -159,7 +168,7 @@ private View createView(@NonNull LayoutInflater inflater, ViewGroup container) { } } }); - } else if (e.getClass() == ImageElementViewModel.class) { + } else if (e instanceof ImageElementViewModel) { ImageElementViewModel ei = (ImageElementViewModel) e; //subscribe to selection ei.getIsSelected().observe(getViewLifecycleOwner(),(isSelected)-> { @@ -297,12 +306,14 @@ public void onStop() { public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_edit_page, menu); - //set logic to add image + //set logic to add image or video MenuItem addImageItem = menu.findItem(R.id.edit_page_add_image); addImageItem.setOnMenuItemClickListener(menuItem -> { //test alternative Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType("image/*"); + intent.setType("*/*"); + String[] mimetypes = {"image/*", "video/*"}; + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); startActivityForResult(intent, ACTIVITY_ADD_IMAGE); @@ -332,6 +343,38 @@ public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) { return true; }); + if (pageVM.getLdAudioMode().getValue() == PageViewModel.AUDIO_MODE_NONE) { + //disable remove audio + menu.removeItem(R.id.edit_page_audio_remove); + //set logic to add audio file + MenuItem addAudioItem = menu.findItem(R.id.edit_page_audio); + addAudioItem.setOnMenuItemClickListener(menuItem -> { + //test alternative + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("audio/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); + startActivityForResult(intent, ACTIVITY_ADD_AUDIO); + return true; + }); + //set logic to add audio silence + MenuItem addAudioStopItem = menu.findItem(R.id.edit_page_audio_stop); + addAudioStopItem.setOnMenuItemClickListener(menuItem -> { + pageVM.addAudio(null,null); + return true; + }); + } else { + //disable audio add + menu.removeItem(R.id.edit_page_audio); + menu.removeItem(R.id.edit_page_audio_stop); + //set logic to remove audio + MenuItem addAudioRemoveItem = menu.findItem(R.id.edit_page_audio_remove); + addAudioRemoveItem.setOnMenuItemClickListener(menuItem -> { + pageVM.removeAudio(); + return true; + }); + } + //set logic to add text MenuItem addTextItem = menu.findItem(R.id.edit_page_add_text); addTextItem.setOnMenuItemClickListener(menuItem -> { @@ -398,10 +441,22 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } finally { cursor.close(); } - pageVM.addImage(input,mime,displayName,size); + if (mime.startsWith("image")) { + pageVM.addImage(input,mime,displayName,size); + } else { + pageVM.addVideo(input,mime,displayName,size); + } } } break; + case ACTIVITY_ADD_AUDIO: + if (resultCode == Activity.RESULT_OK) { + Uri audioUri = data.getData(); + String mime = requireActivity().getContentResolver().getType(audioUri); + InputStream input = PageBuilder.getInputStreamFromUri(requireActivity().getContentResolver(), audioUri); + pageVM.addAudio(input,mime); + } + break; case ACTIVITY_ADD_PHOTO: if (resultCode == Activity.RESULT_OK) { pageVM.addImage(pendingPhotoFile); diff --git a/app/src/main/java/fr/nuage/souvenirs/view/EditPageListAdapter.java b/app/src/main/java/fr/nuage/souvenirs/view/EditPageListAdapter.java index 51e456a..9f84cce 100755 --- a/app/src/main/java/fr/nuage/souvenirs/view/EditPageListAdapter.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/EditPageListAdapter.java @@ -20,11 +20,11 @@ import fr.nuage.souvenirs.databinding.ImageElementViewBinding; import fr.nuage.souvenirs.databinding.TextElementViewShowBinding; import fr.nuage.souvenirs.view.helpers.EditItemTouchHelper; +import fr.nuage.souvenirs.viewmodel.AudioElementViewModel; import fr.nuage.souvenirs.viewmodel.ElementViewModel; import fr.nuage.souvenirs.viewmodel.ImageElementViewModel; import fr.nuage.souvenirs.viewmodel.PageDiffUtilCallback; import fr.nuage.souvenirs.viewmodel.PageViewModel; -import fr.nuage.souvenirs.viewmodel.PaintElementViewModel; import fr.nuage.souvenirs.viewmodel.TextElementViewModel; public class EditPageListAdapter extends RecyclerView.Adapter implements EditItemTouchHelper.ItemTouchHelperAdapter { @@ -73,7 +73,7 @@ public void onBindViewHolder(@NonNull EditPageListAdapter.ViewHolder holder, int PageViewModel page = mPages.get(position); holder.bind(page,(EditAlbumFragment) this.mFragment); //listen to elements changes - page.getElements().observe(mFragment, new Observer>() { + page.getLdElements().observe(mFragment, new Observer>() { @Override public void onChanged(@Nullable ArrayList elementViewModels) { //remove all view @@ -90,7 +90,7 @@ public void onChanged(@Nullable ArrayList elementViewModels) { binding.setLifecycleOwner(mFragment); binding.setElement((TextElementViewModel) e); binding.executePendingBindings(); - } else if (e.getClass() == ImageElementViewModel.class || e.getClass() == PaintElementViewModel.class) { + } else if (e instanceof ImageElementViewModel) { ImageElementViewModel ei = (ImageElementViewModel)e; //load xml layout and bind data ImageElementViewBinding binding = DataBindingUtil.inflate(inflater, R.layout.image_element_view,layout,false); @@ -98,10 +98,12 @@ public void onChanged(@Nullable ArrayList elementViewModels) { binding.setLifecycleOwner(mFragment); binding.setElement(ei); binding.executePendingBindings(); + } else if (e.getClass() == AudioElementViewModel.class) { + continue; } else { - //unknown element : display default view - inflater.inflate(R.layout.unknown_element_view,layout,true); - ImageView unknownImage = layout.findViewById(R.id.unknown_imageview); + //unknown element : display default view + inflater.inflate(R.layout.unknown_element_view,layout,true); + ImageView unknownImage = layout.findViewById(R.id.unknown_imageview); } } } diff --git a/app/src/main/java/fr/nuage/souvenirs/view/PageView.java b/app/src/main/java/fr/nuage/souvenirs/view/PageView.java index c711612..35a367a 100644 --- a/app/src/main/java/fr/nuage/souvenirs/view/PageView.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/PageView.java @@ -13,11 +13,13 @@ import fr.nuage.souvenirs.R; import fr.nuage.souvenirs.databinding.PageViewBinding; import fr.nuage.souvenirs.view.helpers.ViewGenerator; +import fr.nuage.souvenirs.viewmodel.AudioElementViewModel; import fr.nuage.souvenirs.viewmodel.ElementViewModel; import fr.nuage.souvenirs.viewmodel.ImageElementViewModel; import fr.nuage.souvenirs.viewmodel.PageViewModel; import fr.nuage.souvenirs.viewmodel.PaintElementViewModel; import fr.nuage.souvenirs.viewmodel.TextElementViewModel; +import fr.nuage.souvenirs.viewmodel.VideoElementViewModel; public class PageView extends ConstraintLayout { @@ -59,7 +61,7 @@ private void initView() { ConstraintLayout pageLayout = binding.pageLayout; //listen to elements changes - pageViewModel.getElements().observe((AppCompatActivity)getContext(), elementViewModels -> { + pageViewModel.getLdElements().observe((AppCompatActivity)getContext(), elementViewModels -> { //remove all pageLayout.removeAllViewsInLayout(); //rebuild layout @@ -75,9 +77,14 @@ private void initView() { } else if (e.getClass() == PaintElementViewModel.class) { PaintElementViewModel ep = (PaintElementViewModel) e; ViewGenerator.generateView(pageViewModel, ep, pageLayout, (AppCompatActivity)getContext()); + } else if (e.getClass() == VideoElementViewModel.class) { + VideoElementViewModel ep = (VideoElementViewModel) e; + ViewGenerator.generateView(pageViewModel, ep, pageLayout, (AppCompatActivity)getContext()); + } else if (e.getClass() == AudioElementViewModel.class) { + continue; } else { - //unknown element : display default view - inflater1.inflate(R.layout.unknown_element_view, pageLayout, true); + //unknown element : display default view + inflater1.inflate(R.layout.unknown_element_view, pageLayout, true); } } } diff --git a/app/src/main/java/fr/nuage/souvenirs/view/PdfPrepareAlbumFragment.java b/app/src/main/java/fr/nuage/souvenirs/view/PdfPrepareAlbumFragment.java index bdeb7e0..315ad58 100644 --- a/app/src/main/java/fr/nuage/souvenirs/view/PdfPrepareAlbumFragment.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/PdfPrepareAlbumFragment.java @@ -2,7 +2,6 @@ import android.app.PendingIntent; import android.content.Intent; -import android.database.DataSetObserver; import android.graphics.pdf.PdfDocument; import android.net.Uri; import android.os.Bundle; @@ -14,22 +13,14 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -import android.widget.ListView; -import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.FileProvider; -import androidx.databinding.DataBindingUtil; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.Navigation; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.ListAdapter; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.Glide; import java.io.File; import java.io.FileNotFoundException; @@ -43,18 +34,10 @@ import fr.nuage.souvenirs.AlbumListActivity; import fr.nuage.souvenirs.R; -import fr.nuage.souvenirs.databinding.ImageElementViewBinding; -import fr.nuage.souvenirs.databinding.ShowItemPageListBinding; -import fr.nuage.souvenirs.databinding.TextElementViewShowBinding; import fr.nuage.souvenirs.viewmodel.AlbumListViewModel; import fr.nuage.souvenirs.viewmodel.AlbumListViewModelFactory; import fr.nuage.souvenirs.viewmodel.AlbumViewModel; -import fr.nuage.souvenirs.viewmodel.ElementViewModel; -import fr.nuage.souvenirs.viewmodel.ImageElementViewModel; -import fr.nuage.souvenirs.viewmodel.PageDiffUtilCallback; import fr.nuage.souvenirs.viewmodel.PageViewModel; -import fr.nuage.souvenirs.viewmodel.PaintElementViewModel; -import fr.nuage.souvenirs.viewmodel.TextElementViewModel; public class PdfPrepareAlbumFragment extends Fragment { @@ -97,7 +80,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mainLayout.setLayoutParams(params); //start page generation - albumVM.getPages().observe(getViewLifecycleOwner(), pageViewModels -> { + albumVM.getLdPages().observe(getViewLifecycleOwner(), pageViewModels -> { if (pageViewModels != null) { ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.schedule(() -> { diff --git a/app/src/main/java/fr/nuage/souvenirs/view/SelectPageStyleFragment.java b/app/src/main/java/fr/nuage/souvenirs/view/SelectPageStyleFragment.java index 70ed29b..89a4537 100644 --- a/app/src/main/java/fr/nuage/souvenirs/view/SelectPageStyleFragment.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/SelectPageStyleFragment.java @@ -81,12 +81,9 @@ public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle s View pageView = pageBuilder.genPreview(i,previewGrid,inflater); pageView.setId(View.generateViewId()); final int j = i; - pageView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (listener != null) { - listener.onStyleSelected(j); - } + pageView.setOnClickListener(v -> { + if (listener != null) { + listener.onStyleSelected(j); } }); previewGrid.addView(pageView); diff --git a/app/src/main/java/fr/nuage/souvenirs/view/ShowAlbumFragment.java b/app/src/main/java/fr/nuage/souvenirs/view/ShowAlbumFragment.java index 60fa19c..45d1397 100644 --- a/app/src/main/java/fr/nuage/souvenirs/view/ShowAlbumFragment.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/ShowAlbumFragment.java @@ -28,12 +28,12 @@ import fr.nuage.souvenirs.AlbumListActivity; import fr.nuage.souvenirs.R; +import fr.nuage.souvenirs.view.helpers.AudioPlayer; import fr.nuage.souvenirs.viewmodel.AlbumListViewModel; import fr.nuage.souvenirs.viewmodel.AlbumListViewModelFactory; import fr.nuage.souvenirs.viewmodel.AlbumViewModel; import fr.nuage.souvenirs.viewmodel.PageViewModel; import fr.nuage.souvenirs.viewmodel.ShareAlbumAsyncTask; -import fr.nuage.souvenirs.viewmodel.utils.NCUtils; public class ShowAlbumFragment extends Fragment { @@ -42,7 +42,7 @@ public class ShowAlbumFragment extends Fragment { private ShowPageListAdapter pageListAdapter; private RecyclerView pageListRecyclerView; private AlbumViewModel albumVM; - + private AudioPlayer audioPlayer; @Override public void onCreate(Bundle savedInstanceState) { @@ -67,10 +67,15 @@ public void onStart() { ((AlbumListActivity)getActivity()).transparentAppbar(true); } } + //init audio player + audioPlayer = new AudioPlayer(albumVM); + pageListRecyclerView.setOnScrollChangeListener(audioPlayer); } @Override public void onStop() { + //stop audio player + audioPlayer.stop(); //restore activity scrolling if (getActivity().getClass().equals(AlbumListActivity.class)) { ((AlbumListActivity)getActivity()).transparentAppbar(false); @@ -89,9 +94,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, pageListRecyclerView = v.findViewById(R.id.page_list); //fill recyclerview - pageListAdapter = new ShowPageListAdapter(albumVM.getPages(),this,albumVM); + pageListAdapter = new ShowPageListAdapter(albumVM.getLdPages(),this,albumVM); pageListRecyclerView.setAdapter(pageListAdapter); - albumVM.getPages().observe(getViewLifecycleOwner(), new Observer>() { + albumVM.getLdPages().observe(getViewLifecycleOwner(), new Observer>() { @Override public void onChanged(@Nullable ArrayList PageViewModels) { pageListAdapter.updateList(PageViewModels); @@ -173,7 +178,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onResume() { super.onResume(); //refresh page list in case of edit - albumVM.update(); + //albumVM.update(); } } diff --git a/app/src/main/java/fr/nuage/souvenirs/view/ShowPageListAdapter.java b/app/src/main/java/fr/nuage/souvenirs/view/ShowPageListAdapter.java index 42e01ae..4fc004b 100755 --- a/app/src/main/java/fr/nuage/souvenirs/view/ShowPageListAdapter.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/ShowPageListAdapter.java @@ -1,9 +1,9 @@ package fr.nuage.souvenirs.view; import android.content.Intent; +import android.media.MediaPlayer; import android.net.Uri; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; @@ -24,14 +24,17 @@ import fr.nuage.souvenirs.databinding.ImageElementViewBinding; import fr.nuage.souvenirs.databinding.ShowItemPageListBinding; import fr.nuage.souvenirs.databinding.TextElementViewShowBinding; +import fr.nuage.souvenirs.databinding.VideoElementViewBinding; import fr.nuage.souvenirs.model.ImageElement; import fr.nuage.souvenirs.viewmodel.AlbumViewModel; +import fr.nuage.souvenirs.viewmodel.AudioElementViewModel; import fr.nuage.souvenirs.viewmodel.ElementViewModel; import fr.nuage.souvenirs.viewmodel.ImageElementViewModel; import fr.nuage.souvenirs.viewmodel.PageDiffUtilCallback; import fr.nuage.souvenirs.viewmodel.PageViewModel; import fr.nuage.souvenirs.viewmodel.PaintElementViewModel; import fr.nuage.souvenirs.viewmodel.TextElementViewModel; +import fr.nuage.souvenirs.viewmodel.VideoElementViewModel; public class ShowPageListAdapter extends RecyclerView.Adapter { private ArrayList mPages = new ArrayList<>(); @@ -83,7 +86,7 @@ public void onBindViewHolder(@NonNull ShowPageListAdapter.ViewHolder holder, int PageViewModel page = mPages.get(position); //listen to elements changes - page.getElements().observe(mFragment, new Observer>() { + page.getLdElements().observe(mFragment, new Observer>() { @Override public void onChanged(@Nullable ArrayList elementViewModels) { //remove all view @@ -100,7 +103,48 @@ public void onChanged(@Nullable ArrayList elementViewModels) { binding.setElement((TextElementViewModel) e); binding.executePendingBindings(); layout.addView(binding.getRoot()); - } else if (e.getClass() == ImageElementViewModel.class || e.getClass() == PaintElementViewModel.class) { + } else if (e instanceof VideoElementViewModel) { + //load xml layout and bind data + VideoElementViewBinding binding = DataBindingUtil.inflate(inflater, R.layout.video_element_view,layout,false); + binding.setLifecycleOwner(mFragment); + binding.setElement((VideoElementViewModel) e); + binding.executePendingBindings(); + ((VideoElementViewModel) e).getVideoPath().observe(mFragment, binding.imageVideoview::setVideoPath); + ((VideoElementViewModel) e).getIsPlaying().observe(mFragment, isPlaying -> { + if (isPlaying) { + if (binding.imageVideoview.isPlaying()) { + binding.imageVideoview.resume(); + } else { + binding.imageVideoview.start(); + } + } else { + binding.imageVideoview.pause(); + } + }); + //set on click event + binding.imageVideoview.setOnClickListener(view -> { + //open view intent on video + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + Uri imUri = FileProvider.getUriForFile(mFragment.getContext(), mFragment.getContext().getPackageName() + ".provider", new File(((VideoElementViewModel) e).getVideoPath().getValue())); + intent.setDataAndType(imUri, "video/*"); + mFragment.getActivity().startActivity(intent); + }); + //scale to fill/crop canvas + binding.imageVideoview.setOnPreparedListener(mp -> { + float videoRatio = mp.getVideoWidth() / (float) mp.getVideoHeight(); + float screenRatio = binding.imageVideoview.getWidth() / (float) + binding.imageVideoview.getHeight(); + float scaleX = videoRatio / screenRatio; + if (scaleX >= 1f) { + binding.imageVideoview.setScaleX(scaleX); + } else { + binding.imageVideoview.setScaleY(1f / scaleX); + } + }); + layout.addView(binding.getRoot()); + } else if (e instanceof ImageElementViewModel) { //load xml layout and bind data ImageElementViewBinding binding = DataBindingUtil.inflate(inflater, R.layout.image_element_view,layout,false); binding.setLifecycleOwner(mFragment); @@ -108,36 +152,35 @@ public void onChanged(@Nullable ArrayList elementViewModels) { binding.executePendingBindings(); //do not listen to click if paintelement if (e.getClass() != PaintElementViewModel.class) { - binding.imageImageview.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - //special handling for panorama - if (((ImageElementViewModel) e).getIsPano().getValue()) { - //open PanoViewerActivity - Intent intent = new Intent(); - intent.setClass(mFragment.getContext().getApplicationContext(), PanoViewerActivity.class); - intent.setAction(Intent.ACTION_SEND); - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - Uri imUri = FileProvider.getUriForFile(mFragment.getContext(), mFragment.getContext().getPackageName() + ".provider", new File(((ImageElementViewModel) e).getImagePath().getValue())); - intent.setType(ImageElement.GOOGLE_PANORAMA_360_MIMETYPE); - intent.putExtra(Intent.EXTRA_STREAM,imUri); - mFragment.getActivity().startActivity(intent); - } else { - //open view intent on image - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - Uri imUri = FileProvider.getUriForFile(mFragment.getContext(), mFragment.getContext().getPackageName() + ".provider", new File(((ImageElementViewModel) e).getImagePath().getValue())); - intent.setDataAndType(imUri, "image/*"); - mFragment.getActivity().startActivity(intent); - } + binding.imageImageview.setOnClickListener(view -> { + //special handling for panorama + if (((ImageElementViewModel) e).getIsPano().getValue()) { + //open PanoViewerActivity + Intent intent = new Intent(); + intent.setClass(mFragment.getContext().getApplicationContext(), PanoViewerActivity.class); + intent.setAction(Intent.ACTION_SEND); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + Uri imUri = FileProvider.getUriForFile(mFragment.getContext(), mFragment.getContext().getPackageName() + ".provider", new File(((ImageElementViewModel) e).getImagePath().getValue())); + intent.setType(ImageElement.GOOGLE_PANORAMA_360_MIMETYPE); + intent.putExtra(Intent.EXTRA_STREAM,imUri); + mFragment.getActivity().startActivity(intent); + } else { + //open view intent on image + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + Uri imUri = FileProvider.getUriForFile(mFragment.getContext(), mFragment.getContext().getPackageName() + ".provider", new File(((ImageElementViewModel) e).getImagePath().getValue())); + intent.setDataAndType(imUri, "image/*"); + mFragment.getActivity().startActivity(intent); } }); } layout.addView(binding.getRoot()); + } else if (e.getClass() == AudioElementViewModel.class) { + continue; } else { - //unknown element : display default view - inflater.inflate(R.layout.unknown_element_view,layout,true); + //unknown element : display default view + inflater.inflate(R.layout.unknown_element_view,layout,true); } } } diff --git a/app/src/main/java/fr/nuage/souvenirs/view/helpers/AudioPlayer.java b/app/src/main/java/fr/nuage/souvenirs/view/helpers/AudioPlayer.java new file mode 100644 index 0000000..3ba5202 --- /dev/null +++ b/app/src/main/java/fr/nuage/souvenirs/view/helpers/AudioPlayer.java @@ -0,0 +1,110 @@ +package fr.nuage.souvenirs.view.helpers; + + +import android.content.Context; +import android.media.AudioAttributes; +import android.media.MediaPlayer; +import android.net.Uri; +import android.util.Log; +import android.view.View; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.io.IOException; +import java.util.ArrayList; + +import fr.nuage.souvenirs.model.AudioElement; +import fr.nuage.souvenirs.viewmodel.AlbumViewModel; +import fr.nuage.souvenirs.viewmodel.AudioElementViewModel; +import fr.nuage.souvenirs.viewmodel.PageViewModel; +import fr.nuage.souvenirs.viewmodel.VideoElementViewModel; + +public class AudioPlayer implements View.OnScrollChangeListener { + + private AlbumViewModel albumViewModel; + private MediaPlayer mediaPlayer; + private int lastPosition; + + public AudioPlayer(AlbumViewModel albumViewModel) { + this.albumViewModel = albumViewModel; + //init media player + this.mediaPlayer = new MediaPlayer(); + mediaPlayer.setAudioAttributes( + new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build() + ); + + } + + private void play(String audioPath) { + if (mediaPlayer.isPlaying()) { + noplay(); + } + try { + mediaPlayer.setDataSource(audioPath); + mediaPlayer.prepare(); + mediaPlayer.start(); + } catch (IOException e) { + Log.w(getClass().getName(),"Error when playing audio file :"+audioPath); + } + } + + private void noplay() { + mediaPlayer.stop(); + mediaPlayer.reset(); + } + + public void stop() { + mediaPlayer.stop(); + mediaPlayer.release(); + mediaPlayer = null; + } + + @Override + public void onScrollChange(View view, int i, int i1, int i2, int i3) { + if (view instanceof RecyclerView) { + RecyclerView.LayoutManager layoutManager = ((RecyclerView)view).getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + int firstPos = ((LinearLayoutManager)layoutManager).findFirstCompletelyVisibleItemPosition(); + if (firstPos == -1) { + return; + } + //for audio + if ((lastPosition < firstPos) || (firstPos == 0)) { + PageViewModel pageViewModel = albumViewModel.getPage(firstPos); + AudioElement audioElement = pageViewModel.getAudioElement(); + if (audioElement != null) { + if (audioElement.isStop()) { + noplay(); + } else { + String audioPath = audioElement.getAudioPath(); + play(audioPath); + } + } + } + //for video + if ((lastPosition != firstPos) || (firstPos == 0)) { + Log.d(getClass().getName(), "Page scroll : "+lastPosition +"/"+ firstPos); + //start video if one present on new page + PageViewModel pageViewModel = albumViewModel.getPage(firstPos); + ArrayList videos = pageViewModel.getVideoElements(); + for (VideoElementViewModel video : videos) { + video.setIsPlaying(true); + } + //stop videos on previous pages + if (lastPosition != firstPos) { + pageViewModel = albumViewModel.getPage(lastPosition); + videos = pageViewModel.getVideoElements(); + for (VideoElementViewModel video : videos) { + video.setIsPlaying(false); + } + } + } + lastPosition = firstPos; + } + } + } +} diff --git a/app/src/main/java/fr/nuage/souvenirs/view/helpers/ElementMoveDragListener.java b/app/src/main/java/fr/nuage/souvenirs/view/helpers/ElementMoveDragListener.java index c673524..88eb60e 100644 --- a/app/src/main/java/fr/nuage/souvenirs/view/helpers/ElementMoveDragListener.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/helpers/ElementMoveDragListener.java @@ -216,7 +216,7 @@ public boolean onLongClick(View view) { public void onClick(View view) { if (!pageVM.getPaintMode()) { if (!view.isSelected()) { - if (elVM.getClass().equals(ImageElementViewModel.class)) { + if (elVM instanceof ImageElementViewModel) { elVM.setSelected(true); } if (elVM.getClass().equals(TextElementViewModel.class)) { diff --git a/app/src/main/java/fr/nuage/souvenirs/view/helpers/ViewGenerator.java b/app/src/main/java/fr/nuage/souvenirs/view/helpers/ViewGenerator.java index 97f07f5..649cdfd 100644 --- a/app/src/main/java/fr/nuage/souvenirs/view/helpers/ViewGenerator.java +++ b/app/src/main/java/fr/nuage/souvenirs/view/helpers/ViewGenerator.java @@ -1,8 +1,11 @@ package fr.nuage.souvenirs.view.helpers; +import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.os.Build; +import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.View; import android.widget.ImageView; @@ -10,9 +13,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; import androidx.core.content.res.ResourcesCompat; +import androidx.core.widget.ImageViewCompat; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.Observer; @@ -21,40 +26,71 @@ import com.bumptech.glide.request.transition.Transition; import java.io.File; -import java.util.UUID; import fr.nuage.souvenirs.R; import fr.nuage.souvenirs.model.ImageElement; import fr.nuage.souvenirs.view.ImageElementView; import fr.nuage.souvenirs.view.PaintElementView; import fr.nuage.souvenirs.view.TextElementView; +import fr.nuage.souvenirs.viewmodel.ElementViewModel; import fr.nuage.souvenirs.viewmodel.ImageElementViewModel; import fr.nuage.souvenirs.viewmodel.PageViewModel; import fr.nuage.souvenirs.viewmodel.PaintElementViewModel; import fr.nuage.souvenirs.viewmodel.TextElementViewModel; +import fr.nuage.souvenirs.viewmodel.VideoElementViewModel; public class ViewGenerator { + public static int convertDpToPixel(int dp, Context context){ + return (int)(dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT)); + } + + private static View applyDefaultElementView(ConstraintLayout parentView, View view, ElementViewModel elementViewModel, + LifecycleOwner lifecycleOwner) { + view.setId(View.generateViewId()); + //add to parent + parentView.addView(view); + //set constraints + ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) view.getLayoutParams(); + layoutParams.width = 0; + layoutParams.height = 0; + ConstraintSet set = new ConstraintSet(); + set.clone(parentView); + set.connect(view.getId(),ConstraintSet.LEFT,ConstraintSet.PARENT_ID,ConstraintSet.LEFT,0); + set.connect(view.getId(),ConstraintSet.RIGHT,ConstraintSet.PARENT_ID,ConstraintSet.RIGHT,0); + set.connect(view.getId(),ConstraintSet.TOP,ConstraintSet.PARENT_ID,ConstraintSet.TOP,0); + set.connect(view.getId(),ConstraintSet.BOTTOM,ConstraintSet.PARENT_ID,ConstraintSet.BOTTOM,0); + set.applyTo(parentView); + //observe change on position inside page + Observer observer = integer -> { + if ((elementViewModel.getRight().getValue() != null) && (elementViewModel.getLeft().getValue() != null) + && (elementViewModel.getTop().getValue() != null) && (elementViewModel.getBottom().getValue() != null)) { + ConstraintSet set1 = new ConstraintSet(); + set1.clone(parentView); + set1.constrainPercentWidth(view.getId(), ((float) elementViewModel.getRight().getValue() - elementViewModel.getLeft().getValue()) / 100); + set1.setHorizontalBias(view.getId(), ((float) elementViewModel.getLeft().getValue()) / (100 - elementViewModel.getRight().getValue() + elementViewModel.getLeft().getValue())); + set1.constrainPercentHeight(view.getId(), ((float) elementViewModel.getBottom().getValue() - elementViewModel.getTop().getValue()) / 100); + set1.setVerticalBias(view.getId(), ((float) elementViewModel.getTop().getValue()) / (100 - elementViewModel.getBottom().getValue() + elementViewModel.getTop().getValue())); + set1.applyTo(parentView); + } + }; + elementViewModel.getLeft().observe(lifecycleOwner, observer); + elementViewModel.getRight().observe(lifecycleOwner, observer); + elementViewModel.getTop().observe(lifecycleOwner, observer); + elementViewModel.getBottom().observe(lifecycleOwner, observer); + elementViewModel.getId().observe(lifecycleOwner, view::setTag); + elementViewModel.getIsSelected().observe(lifecycleOwner, view::setSelected); + return view; + } + /* generate view based on paintElementViewModel */ public static PaintElementView generateView(PageViewModel pageViewModel, PaintElementViewModel paintElementViewModel, ConstraintLayout parentViewGroup, LifecycleOwner lifecycleOwner) { //gen paintview PaintElementView paintElementView = new PaintElementView(parentViewGroup.getContext(),pageViewModel,paintElementViewModel); - paintElementView.setId(View.generateViewId()); - //add to parent - parentViewGroup.addView(paintElementView); - //set constraints - ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) paintElementView.getLayoutParams(); - layoutParams.width = 0; - layoutParams.height = 0; - ConstraintSet set = new ConstraintSet(); - set.clone(parentViewGroup); - set.connect(paintElementView.getId(),ConstraintSet.LEFT,ConstraintSet.PARENT_ID,ConstraintSet.LEFT,0); - set.connect(paintElementView.getId(),ConstraintSet.RIGHT,ConstraintSet.PARENT_ID,ConstraintSet.RIGHT,0); - set.connect(paintElementView.getId(),ConstraintSet.TOP,ConstraintSet.PARENT_ID,ConstraintSet.TOP,0); - set.connect(paintElementView.getId(),ConstraintSet.BOTTOM,ConstraintSet.PARENT_ID,ConstraintSet.BOTTOM,0); - set.applyTo(parentViewGroup); + //apply default params + applyDefaultElementView(parentViewGroup,paintElementView,paintElementViewModel,lifecycleOwner); //define observables for data binding paintElementViewModel.getImagePath().observe(lifecycleOwner, s -> { if (s != null) { @@ -74,36 +110,37 @@ public void onLoadCleared(@Nullable Drawable placeholder) { } } } }); - ConstraintSet set1 = new ConstraintSet(); - set1.clone(parentViewGroup); - set1.constrainPercentWidth(paintElementView.getId(), 1); - set1.constrainPercentHeight(paintElementView.getId(), 1); - set1.applyTo(parentViewGroup); - return paintElementView; } + public static View generateView(PageViewModel pageViewModel, VideoElementViewModel videoElementViewModel, ConstraintLayout parentViewGroup, LifecycleOwner lifecycleOwner) { + View imageView = generateView(pageViewModel,(ImageElementViewModel) videoElementViewModel,parentViewGroup,lifecycleOwner); + //gen video icon + AppCompatImageView imageViewIcon = new AppCompatImageView(parentViewGroup.getContext()); + imageViewIcon.setId(View.generateViewId()); + imageViewIcon.setImageResource(R.drawable.ic_baseline_videocam_24); + parentViewGroup.addView(imageViewIcon); + ImageViewCompat.setImageTintList(imageViewIcon, ColorStateList.valueOf(parentViewGroup.getResources().getColor(R.color.secondaryColor))); + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(parentViewGroup); + constraintSet.constrainWidth(imageViewIcon.getId(),convertDpToPixel(48,parentViewGroup.getContext())); + constraintSet.constrainHeight(imageViewIcon.getId(),convertDpToPixel(48,parentViewGroup.getContext())); + constraintSet.connect(imageViewIcon.getId(),ConstraintSet.TOP,imageView.getId(),ConstraintSet.TOP,convertDpToPixel(8,parentViewGroup.getContext())); + constraintSet.connect(imageViewIcon.getId(),ConstraintSet.LEFT,imageView.getId(),ConstraintSet.LEFT,convertDpToPixel(8,parentViewGroup.getContext())); + constraintSet.applyTo(parentViewGroup); + + return imageView; + } + /* generate view based on imageelementviewmodel */ public static View generateView(PageViewModel pageViewModel, ImageElementViewModel imageElementViewModel, ConstraintLayout parentViewGroup, LifecycleOwner lifecycleOwner) { //gen imageview ImageElementView imageView = new ImageElementView(parentViewGroup.getContext(),pageViewModel,imageElementViewModel); - imageView.setId(View.generateViewId()); imageView.setScrollContainer(true); - //add to parent - parentViewGroup.addView(imageView); - //set constraints - ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) imageView.getLayoutParams(); - layoutParams.width = 0; - layoutParams.height = 0; - ConstraintSet set = new ConstraintSet(); - set.clone(parentViewGroup); - set.connect(imageView.getId(),ConstraintSet.LEFT,ConstraintSet.PARENT_ID,ConstraintSet.LEFT,0); - set.connect(imageView.getId(),ConstraintSet.RIGHT,ConstraintSet.PARENT_ID,ConstraintSet.RIGHT,0); - set.connect(imageView.getId(),ConstraintSet.TOP,ConstraintSet.PARENT_ID,ConstraintSet.TOP,0); - set.connect(imageView.getId(),ConstraintSet.BOTTOM,ConstraintSet.PARENT_ID,ConstraintSet.BOTTOM,0); - set.applyTo(parentViewGroup); + //apply default params + applyDefaultElementView(parentViewGroup,imageView,imageElementViewModel,lifecycleOwner); //define observables for data binding imageElementViewModel.getTransformType().observe(lifecycleOwner, (Observer) i -> { if (i != null) { @@ -127,24 +164,6 @@ public static View generateView(PageViewModel pageViewModel, ImageElementViewMod } } }); - Observer observer = integer -> { - if ((imageElementViewModel.getRight().getValue() != null) && (imageElementViewModel.getLeft().getValue() != null) - && (imageElementViewModel.getTop().getValue() != null) && (imageElementViewModel.getBottom().getValue() != null)) { - ConstraintSet set1 = new ConstraintSet(); - set1.clone(parentViewGroup); - set1.constrainPercentWidth(imageView.getId(), ((float) imageElementViewModel.getRight().getValue() - imageElementViewModel.getLeft().getValue()) / 100); - set1.setHorizontalBias(imageView.getId(), ((float) imageElementViewModel.getLeft().getValue()) / (100 - imageElementViewModel.getRight().getValue() + imageElementViewModel.getLeft().getValue())); - set1.constrainPercentHeight(imageView.getId(), ((float) imageElementViewModel.getBottom().getValue() - imageElementViewModel.getTop().getValue()) / 100); - set1.setVerticalBias(imageView.getId(), ((float) imageElementViewModel.getTop().getValue()) / (100 - imageElementViewModel.getBottom().getValue() + imageElementViewModel.getTop().getValue())); - set1.applyTo(parentViewGroup); - } - }; - imageElementViewModel.getLeft().observe(lifecycleOwner, observer); - imageElementViewModel.getRight().observe(lifecycleOwner, observer); - imageElementViewModel.getTop().observe(lifecycleOwner, observer); - imageElementViewModel.getBottom().observe(lifecycleOwner, observer); - imageElementViewModel.getId().observe(lifecycleOwner, imageView::setTag); - imageElementViewModel.getIsSelected().observe(lifecycleOwner, imageView::setSelected); Observer zoomOffsetObserver = integer -> { if ((imageElementViewModel.getOffsetX().getValue() != null) && (imageElementViewModel.getOffsetY().getValue() != null) && (imageElementViewModel.getZoom().getValue() != null)) { imageView.updateMatrix(); @@ -153,6 +172,23 @@ public static View generateView(PageViewModel pageViewModel, ImageElementViewMod imageElementViewModel.getOffsetX().observe(lifecycleOwner, zoomOffsetObserver); imageElementViewModel.getOffsetY().observe(lifecycleOwner, zoomOffsetObserver); imageElementViewModel.getZoom().observe(lifecycleOwner, zoomOffsetObserver); + imageElementViewModel.getIsPano().observe(lifecycleOwner, aBoolean -> { + if (aBoolean) { + //gen pano icon + AppCompatImageView imageViewIcon = new AppCompatImageView(parentViewGroup.getContext()); + imageViewIcon.setId(View.generateViewId()); + imageViewIcon.setImageResource(R.drawable.ic_baseline_panorama_horizontal_24); + parentViewGroup.addView(imageViewIcon); + ImageViewCompat.setImageTintList(imageViewIcon, ColorStateList.valueOf(parentViewGroup.getResources().getColor(R.color.secondaryColor))); + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(parentViewGroup); + constraintSet.constrainWidth(imageViewIcon.getId(),convertDpToPixel(48,parentViewGroup.getContext())); + constraintSet.constrainHeight(imageViewIcon.getId(),convertDpToPixel(48,parentViewGroup.getContext())); + constraintSet.connect(imageViewIcon.getId(),ConstraintSet.TOP,imageView.getId(),ConstraintSet.TOP,convertDpToPixel(8,parentViewGroup.getContext())); + constraintSet.connect(imageViewIcon.getId(),ConstraintSet.LEFT,imageView.getId(),ConstraintSet.LEFT,convertDpToPixel(8,parentViewGroup.getContext())); + constraintSet.applyTo(parentViewGroup); + } + }); return imageView; } @@ -162,23 +198,11 @@ public static View generateView(PageViewModel pageViewModel, ImageElementViewMod public static View generateView(PageViewModel pageViewModel, TextElementViewModel textElementViewModel, ConstraintLayout parentViewGroup, LifecycleOwner lifecycleOwner) { //gen textview TextView textView = new TextElementView(parentViewGroup.getContext(),pageViewModel,textElementViewModel); - textView.setId(View.generateViewId()); textView.setClickable(true); textView.setHint(R.string.text_element_hint); textView.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - //add to parent - parentViewGroup.addView(textView); - //set constraints - ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) textView.getLayoutParams(); - layoutParams.width = 0; - layoutParams.height = 0; - ConstraintSet set = new ConstraintSet(); - set.clone(parentViewGroup); - set.connect(textView.getId(),ConstraintSet.LEFT,ConstraintSet.PARENT_ID,ConstraintSet.LEFT,0); - set.connect(textView.getId(),ConstraintSet.RIGHT,ConstraintSet.PARENT_ID,ConstraintSet.RIGHT,0); - set.connect(textView.getId(),ConstraintSet.TOP,ConstraintSet.PARENT_ID,ConstraintSet.TOP,0); - set.connect(textView.getId(),ConstraintSet.BOTTOM,ConstraintSet.PARENT_ID,ConstraintSet.BOTTOM,0); - set.applyTo(parentViewGroup); + //apply default params + applyDefaultElementView(parentViewGroup,textView,textElementViewModel,lifecycleOwner); //define observables for data binding textElementViewModel.getText().observe(lifecycleOwner, s -> { textView.setText(s); @@ -191,24 +215,6 @@ public static View generateView(PageViewModel pageViewModel, TextElementViewMode } } }); - Observer observer = integer -> { - if ((textElementViewModel.getRight().getValue() != null) && (textElementViewModel.getLeft().getValue() != null) - && (textElementViewModel.getTop().getValue() != null) && (textElementViewModel.getBottom().getValue() != null)) { - ConstraintSet set1 = new ConstraintSet(); - set1.clone(parentViewGroup); - set1.constrainPercentWidth(textView.getId(), ((float) textElementViewModel.getRight().getValue() - textElementViewModel.getLeft().getValue()) / 100); - set1.setHorizontalBias(textView.getId(), ((float) textElementViewModel.getLeft().getValue()) / (100 - textElementViewModel.getRight().getValue() + textElementViewModel.getLeft().getValue())); - set1.constrainPercentHeight(textView.getId(), ((float) textElementViewModel.getBottom().getValue() - textElementViewModel.getTop().getValue()) / 100); - set1.setVerticalBias(textView.getId(), ((float) textElementViewModel.getTop().getValue()) / (100 - textElementViewModel.getBottom().getValue() + textElementViewModel.getTop().getValue())); - set1.applyTo(parentViewGroup); - } - }; - textElementViewModel.getLeft().observe(lifecycleOwner, observer); - textElementViewModel.getRight().observe(lifecycleOwner, observer); - textElementViewModel.getTop().observe(lifecycleOwner, observer); - textElementViewModel.getBottom().observe(lifecycleOwner, observer); - textElementViewModel.getId().observe(lifecycleOwner, textView::setTag); - textElementViewModel.getIsSelected().observe(lifecycleOwner, textView::setSelected); return textView; } diff --git a/app/src/main/java/fr/nuage/souvenirs/viewmodel/AlbumViewModel.java b/app/src/main/java/fr/nuage/souvenirs/viewmodel/AlbumViewModel.java index b3be533..0702eaf 100755 --- a/app/src/main/java/fr/nuage/souvenirs/viewmodel/AlbumViewModel.java +++ b/app/src/main/java/fr/nuage/souvenirs/viewmodel/AlbumViewModel.java @@ -1,9 +1,7 @@ package fr.nuage.souvenirs.viewmodel; -import android.app.Activity; import android.app.Application; import android.content.Context; -import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.widget.Toast; @@ -14,14 +12,15 @@ import androidx.lifecycle.MediatorLiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Transformations; -import androidx.navigation.Navigation; import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.Locale; import java.util.UUID; +import java.util.stream.IntStream; import fr.nuage.souvenirs.R; import fr.nuage.souvenirs.SyncService; @@ -31,7 +30,6 @@ import fr.nuage.souvenirs.model.TilePageBuilder; import fr.nuage.souvenirs.model.nc.AlbumNC; import fr.nuage.souvenirs.model.nc.AlbumsNC; -import fr.nuage.souvenirs.view.ShowAlbumFragmentDirections; public class AlbumViewModel extends AndroidViewModel { @@ -42,20 +40,21 @@ public class AlbumViewModel extends AndroidViewModel { public static final int NC_STATE_NOSYNC = 4; public static final int NC_STATE_SYNC_IN_PROGRESS = 5; - private MediatorLiveData name = new MediatorLiveData<>(); + private final MediatorLiveData name = new MediatorLiveData<>(); private Album album; private AlbumNC albumNC; private UUID id; - private LiveData> pages = new MutableLiveData<>(); - private MediatorLiveData ldDate = new MediatorLiveData<>(); - private MediatorLiveData ldAlbumImage = new MediatorLiveData<>(); - private MutableLiveData focusPageId = new MutableLiveData<>(); - private MutableLiveData ldHasAlbumNC = new MutableLiveData<>(); - private MutableLiveData ldHasAlbum = new MutableLiveData<>(); + private final ArrayList pages = new ArrayList<>(); + private LiveData> ldPages = new MutableLiveData<>(); + private final MediatorLiveData ldDate = new MediatorLiveData<>(); + private final MediatorLiveData ldAlbumImage = new MediatorLiveData<>(); + private final MutableLiveData focusPageId = new MutableLiveData<>(); + private final MutableLiveData ldHasAlbumNC = new MutableLiveData<>(); + private final MutableLiveData ldHasAlbum = new MutableLiveData<>(); private boolean syncInProgress = false; - private MediatorLiveData ldIsShared = new MediatorLiveData<>(); - private MediatorLiveData ldNCState = new MediatorLiveData<>(); - private MediatorLiveData ldDefaultStyle = new MediatorLiveData<>(); + private final MediatorLiveData ldIsShared = new MediatorLiveData<>(); + private final MediatorLiveData ldNCState = new MediatorLiveData<>(); + private final MediatorLiveData ldDefaultStyle = new MediatorLiveData<>(); private int albumsNCState = AlbumsNC.STATE_NOT_LOADED; public AlbumViewModel(Application app) { @@ -91,6 +90,33 @@ public AlbumNC getAlbumNC() { return this.albumNC; } + private void updatePages(ArrayList pages) { + //remove deleted pages + int i = 0; + while (i < this.pages.size()) { + PageViewModel pvm = this.pages.get(i); + if (pages.stream().filter(page -> page.getId().equals(pvm.getId())).count() == 0) { + this.pages.remove(i); + } else { + i++; + } + } + //create new pages + for (int j = 0; j < pages.size(); j++) { + Page p = pages.get(j); + int vmIndex = IntStream.range(0, this.pages.size()) + .filter(k -> this.pages.get(k).getId().equals(p.getId())) + .findFirst() + .orElse(-1); + if (vmIndex == -1) { + PageViewModel pVM = new PageViewModel(p); + this.pages.add(j,pVM); + } else { + Collections.swap(this.pages,vmIndex,j); + } + } + } + public void setAlbum(Album album) { Album oldAlbum = getAlbum(); this.album = album; @@ -102,7 +128,7 @@ public void run() { //remove previous livedata observers for Album name.removeSource(oldAlbum.getLiveDataName()); ldDate.removeSource(oldAlbum.getLdDate()); - pages = new MutableLiveData<>(); + ldPages = new MutableLiveData<>(); ldAlbumImage.removeSource(oldAlbum.getLdAlbumImage()); ldDefaultStyle.removeSource(oldAlbum.getLdDefaultStyle()); ldNCState.removeSource(oldAlbum.getLdLastEditDate()); @@ -117,13 +143,9 @@ public void run() { ldDate.addSource(album.getLdDate(), date -> { ldDate.setValue((new SimpleDateFormat("dd MMMM yyyy", Locale.FRANCE)).format(date)); }); - pages = Transformations.map(album.getLiveDataPages(), pages -> { - ArrayList out = new ArrayList(); - for (Page p : pages) { - PageViewModel pVM = new PageViewModel(p); - out.add(pVM); - } - return out; + ldPages = Transformations.map(album.getLiveDataPages(), pagesModel -> { + updatePages(pagesModel); + return new ArrayList<>(pages); }); ldAlbumImage.addSource(album.getLdAlbumImage(), imagePath -> { ldAlbumImage.setValue(imagePath); @@ -287,31 +309,36 @@ public int getPosition(PageViewModel page) { if (page == null) { return -1; } - return album.getIndex(page.getPage()); + return getPosition(page.getId()); } public int getPosition(UUID pageId) { - for (Page p : album.getPages()) { + for (PageViewModel p : pages) { if (p.getId().equals(pageId)) { - return album.getPages().indexOf(p); + return pages.indexOf(p); } } - return 0; + return -1; } - public LiveData> getPages() { - return pages; + public LiveData> getLdPages() { + return ldPages; } public String getDataPath() { return album.getDataPath(); } public PageViewModel getPage(int position) { - Page page = album.getPage(position); - if (page == null) { + if (position == -1) { + if (pages.size() == 0) { + return null; + } + return pages.get(pages.size()-1); + } + if (position >= pages.size()) { return null; } - return new PageViewModel(page); + return pages.get(position); } public PageViewModel getPage(UUID id) { @@ -330,6 +357,7 @@ public void setName(String name) { album.setName(name); } +/* should not be used anymore public void update() { if (album != null) { album.load(); @@ -337,7 +365,7 @@ public void update() { if (albumNC != null) { albumNC.load(); } - } + }*/ public UUID getId() { return id; @@ -426,11 +454,8 @@ public MediatorLiveData getLdDefaultStyle() { } public PageViewModel getNextPage(PageViewModel pageVM) { - if (getPages().getValue() == null) { - return null; - } int pos = getPosition(pageVM); - if (pos < getPages().getValue().size()-1) { + if (pos < pages.size()-1) { return getPage(pos + 1); } return null; diff --git a/app/src/main/java/fr/nuage/souvenirs/viewmodel/AudioElementViewModel.java b/app/src/main/java/fr/nuage/souvenirs/viewmodel/AudioElementViewModel.java new file mode 100644 index 0000000..6a8a446 --- /dev/null +++ b/app/src/main/java/fr/nuage/souvenirs/viewmodel/AudioElementViewModel.java @@ -0,0 +1,31 @@ +package fr.nuage.souvenirs.viewmodel; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.Transformations; + +import fr.nuage.souvenirs.model.AudioElement; +import fr.nuage.souvenirs.model.Element; + +public class AudioElementViewModel extends ElementViewModel{ + + private final LiveData audioPath; + private final LiveData stop; + + public AudioElementViewModel(AudioElement e) { + super(e); + audioPath = Transformations.map(e.getLdAudioPath(), audioPath -> audioPath); + stop = Transformations.map(e.getLdStop(), stop -> stop); + } + + public LiveData getAudioPath() { + return audioPath; + } + + public LiveData getStop() { + return stop; + } + + public boolean isStop() { + return ((AudioElement)element).isStop(); + } +} diff --git a/app/src/main/java/fr/nuage/souvenirs/viewmodel/PageDiffUtilCallback.java b/app/src/main/java/fr/nuage/souvenirs/viewmodel/PageDiffUtilCallback.java index db51474..ad9cb8c 100644 --- a/app/src/main/java/fr/nuage/souvenirs/viewmodel/PageDiffUtilCallback.java +++ b/app/src/main/java/fr/nuage/souvenirs/viewmodel/PageDiffUtilCallback.java @@ -31,8 +31,8 @@ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - ArrayList oldElements = oldPages.get(oldItemPosition).getElements().getValue(); - ArrayList newElements = newPages.get(newItemPosition).getElements().getValue(); + ArrayList oldElements = oldPages.get(oldItemPosition).getLdElements().getValue(); + ArrayList newElements = newPages.get(newItemPosition).getLdElements().getValue(); if (newElements == null) { return oldElements == null; } diff --git a/app/src/main/java/fr/nuage/souvenirs/viewmodel/PageViewModel.java b/app/src/main/java/fr/nuage/souvenirs/viewmodel/PageViewModel.java index 354411d..7004082 100755 --- a/app/src/main/java/fr/nuage/souvenirs/viewmodel/PageViewModel.java +++ b/app/src/main/java/fr/nuage/souvenirs/viewmodel/PageViewModel.java @@ -1,7 +1,6 @@ package fr.nuage.souvenirs.viewmodel; import android.graphics.Bitmap; -import android.util.Log; import android.webkit.MimeTypeMap; import androidx.lifecycle.LiveData; @@ -10,13 +9,14 @@ import androidx.lifecycle.ViewModel; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.UUID; +import java.util.stream.IntStream; import fr.nuage.souvenirs.model.Album; +import fr.nuage.souvenirs.model.AudioElement; import fr.nuage.souvenirs.model.Element; import fr.nuage.souvenirs.model.ImageElement; import fr.nuage.souvenirs.model.Page; @@ -25,52 +25,89 @@ import fr.nuage.souvenirs.model.TextElement; import fr.nuage.souvenirs.model.TilePageBuilder; import fr.nuage.souvenirs.model.UnknownElement; +import fr.nuage.souvenirs.model.VideoElement; public class PageViewModel extends ViewModel { + public static final int AUDIO_MODE_NONE = 0; + public static final int AUDIO_MODE_ON = 1; + public static final int AUDIO_MODE_OFF = 2; + private final Page page; - private final LiveData> elements; + private final LiveData> ldElements; + private final ArrayList elements = new ArrayList<>(); private final MutableLiveData paintMode = new MutableLiveData<>(); private final MutableLiveData editMode = new MutableLiveData<>(); + private final LiveData audioMode; + public PageViewModel(Page page) { super(); this.page = page; editMode.postValue(false); paintMode.postValue(false); - elements = Transformations.map(page.getLiveDataElements(), elements -> { - ArrayList out = new ArrayList(); + ldElements = Transformations.map(page.getLiveDataElements(), elementsModel -> { + updateElements(elementsModel); + return new ArrayList<>(elements); + }); + audioMode = Transformations.map(page.getLiveDataElements(), elements -> { + int mode = AUDIO_MODE_NONE; for (Element element : elements) { - ElementViewModel eVM = null; - //check if already exists - ArrayList els = getElements().getValue(); - if (els != null) { - for (ElementViewModel elVM : els) { - if (element.getId().equals(elVM.getId().getValue())) { - eVM = elVM; - break; - } - } - } - if (eVM == null) { - if (element.getClass() == TextElement.class) { - eVM = new TextElementViewModel((TextElement) element); - } else if (element.getClass() == ImageElement.class) { - eVM = new ImageElementViewModel((ImageElement) element); - } else if (element.getClass() == PaintElement.class) { - eVM = new PaintElementViewModel((PaintElement) element); + if (element.getClass().equals(AudioElement.class)) { + AudioElement audioElement = (AudioElement) element; + if (audioElement.isStop()) { + mode = AUDIO_MODE_OFF; } else { - eVM = new UnknownElementViewModel((UnknownElement)element); + mode = AUDIO_MODE_ON; } } - out.add(eVM); } - return out; + return mode; }); } - public LiveData> getElements() { - return elements; + private void updateElements(ArrayList elements) { + //remove deleted pages + int i = 0; + while (i < this.elements.size()) { + ElementViewModel evm = this.elements.get(i); + if (elements.stream().filter(element -> element.getId().equals(evm.getId())).count() == 0) { + this.elements.remove(i); + } else { + i++; + } + } + //create new pages + for (int j = 0; j < elements.size(); j++) { + Element e = elements.get(j); + int vmIndex = IntStream.range(0, this.elements.size()) + .filter(k -> this.elements.get(k).getId().equals(e.getId())) + .findFirst() + .orElse(-1); + if (vmIndex == -1) { + ElementViewModel eVM; + if (e.getClass() == TextElement.class) { + eVM = new TextElementViewModel((TextElement) e); + } else if (e.getClass() == ImageElement.class) { + eVM = new ImageElementViewModel((ImageElement) e); + } else if (e.getClass() == PaintElement.class) { + eVM = new PaintElementViewModel((PaintElement) e); + } else if (e.getClass() == AudioElement.class) { + eVM = new AudioElementViewModel((AudioElement) e); + } else if (e.getClass() == VideoElement.class) { + eVM = new VideoElementViewModel((VideoElement) e); + } else { + eVM = new UnknownElementViewModel((UnknownElement)e); + } + this.elements.add(j,eVM); + } else { + Collections.swap(this.elements,vmIndex,j); + } + } + } + + public LiveData> getLdElements() { + return ldElements; } public UUID getId() { @@ -93,6 +130,10 @@ public MutableLiveData getLdEditMode() { return editMode; } + public LiveData getLdAudioMode() { + return audioMode; + } + public Page getPage() { return page; } @@ -167,8 +208,8 @@ public void setPaintMode(boolean b) { } public PaintElementViewModel getPaintElement() { - if (elements.getValue() != null) { - for (ElementViewModel e : elements.getValue()) { + if (ldElements.getValue() != null) { + for (ElementViewModel e : ldElements.getValue()) { if (e.getClass().equals(PaintElementViewModel.class)) { return (PaintElementViewModel)e; } @@ -189,7 +230,7 @@ public void startPaintMode() { } public ElementViewModel getElement(UUID elementId) { - for (ElementViewModel elementViewModel: getElements().getValue()) { + for (ElementViewModel elementViewModel: elements) { if (elementId.equals(elementViewModel.getId().getValue())) { return elementViewModel; } @@ -197,5 +238,44 @@ public ElementViewModel getElement(UUID elementId) { return null; } + public void addAudio(InputStream inputAudio, String mimeType) { + AudioElement audioElement = page.createAudioElement(); + if (inputAudio == null) { + //insert stop audio + audioElement.setStop(true,true); + } else { + audioElement.setAudio(inputAudio,mimeType); + } + } + + public void removeAudio() { + page.removeAudio(); + } + + public AudioElement getAudioElement() { + for (Element element : page.getElements()) { + if (element.getClass().equals(AudioElement.class)) { + return (AudioElement)element; + } + } + return null; + } + + public void addVideo(InputStream input, String mime, String displayName, int size) { + VideoElement videoElement = page.createVideoElement(); + videoElement.setVideo(input,mime); + videoElement.setName(displayName); + videoElement.setSize(size); + addImage(videoElement); + } + public ArrayList getVideoElements() { + ArrayList out = new ArrayList<>(); + for (ElementViewModel element : elements) { + if (element.getClass().equals(VideoElementViewModel.class)) { + out.add((VideoElementViewModel) element); + } + } + return out; + } } diff --git a/app/src/main/java/fr/nuage/souvenirs/viewmodel/ShareAlbumAsyncTask.java b/app/src/main/java/fr/nuage/souvenirs/viewmodel/ShareAlbumAsyncTask.java index 7a2d670..89728b8 100644 --- a/app/src/main/java/fr/nuage/souvenirs/viewmodel/ShareAlbumAsyncTask.java +++ b/app/src/main/java/fr/nuage/souvenirs/viewmodel/ShareAlbumAsyncTask.java @@ -31,12 +31,9 @@ public ShareAlbumAsyncTask(Context context, AlbumViewModel albumViewModel) { protected Integer doInBackground(Void... voids) { //call to share album - albumViewModel.update(); try { String share_url = APIProvider.getApi().createShare(albumViewModel.getId().toString()).execute().body(); if (share_url != null) { - //update albumNC to get the right share status - albumViewModel.update(); //create intent to share url Intent i = new Intent(Intent.ACTION_SEND); i.setType("text/plain"); diff --git a/app/src/main/java/fr/nuage/souvenirs/viewmodel/VideoElementViewModel.java b/app/src/main/java/fr/nuage/souvenirs/viewmodel/VideoElementViewModel.java new file mode 100644 index 0000000..a5f497c --- /dev/null +++ b/app/src/main/java/fr/nuage/souvenirs/viewmodel/VideoElementViewModel.java @@ -0,0 +1,41 @@ +package fr.nuage.souvenirs.viewmodel; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Transformations; + +import java.io.InputStream; + +import fr.nuage.souvenirs.model.ImageElement; +import fr.nuage.souvenirs.model.VideoElement; + +public class VideoElementViewModel extends ImageElementViewModel { + + private final LiveData videoPath; + private final MutableLiveData isPlaying = new MutableLiveData<>(); + + public VideoElementViewModel(VideoElement e) { + super(e); + videoPath = Transformations.map(e.getLdVideoPath(), videoPath -> videoPath); + } + + public LiveData getVideoPath() { + return videoPath; + } + + public void setVideo(InputStream input, String mimeType) { + ((VideoElement)element).setVideo(input,mimeType); + } + + public void setVideo(String localImagePath, String mimeType) { + ((VideoElement)element).setVideo(localImagePath,mimeType); + } + + public void setIsPlaying(boolean isPlaying) { + this.isPlaying.postValue(isPlaying); + } + + public MutableLiveData getIsPlaying() { + return isPlaying; + } +} diff --git a/app/src/main/res/drawable/ic_baseline_audiotrack_24.xml b/app/src/main/res/drawable/ic_baseline_audiotrack_24.xml new file mode 100644 index 0000000..a7859f1 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_audiotrack_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_videocam_24.xml b/app/src/main/res/drawable/ic_baseline_videocam_24.xml new file mode 100644 index 0000000..34b26df --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_videocam_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_noaudio.xml b/app/src/main/res/drawable/ic_noaudio.xml new file mode 100644 index 0000000..dee31d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_noaudio.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/layout/edit_item_page_list.xml b/app/src/main/res/layout/edit_item_page_list.xml index f593e13..7f480ba 100755 --- a/app/src/main/res/layout/edit_item_page_list.xml +++ b/app/src/main/res/layout/edit_item_page_list.xml @@ -4,6 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + + + + diff --git a/app/src/main/res/layout/fragment_edit_page.xml b/app/src/main/res/layout/fragment_edit_page.xml index 7ff625c..e4bb009 100644 --- a/app/src/main/res/layout/fragment_edit_page.xml +++ b/app/src/main/res/layout/fragment_edit_page.xml @@ -4,6 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + @@ -36,6 +37,19 @@ + + + @@ -48,15 +49,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_edit_page.xml b/app/src/main/res/menu/menu_edit_page.xml index fa16ad2..f84e6bd 100644 --- a/app/src/main/res/menu/menu_edit_page.xml +++ b/app/src/main/res/menu/menu_edit_page.xml @@ -22,6 +22,21 @@ android:icon="@drawable/ic_brush_black_24dp" android:title="@string/edit_page_draw" app:showAsAction="ifRoom" /> + + + Zoom In Zoom Out No camera detected ! + Add audio + Remove audio + Stop audio diff --git a/metadata/en-US/changelogs/21.txt b/metadata/en-US/changelogs/21.txt new file mode 100644 index 0000000..04cdc5b --- /dev/null +++ b/metadata/en-US/changelogs/21.txt @@ -0,0 +1,4 @@ +New Feature: Add audio support. Audio will play on view mode until an audio stop command is set +New Feature: Add video support. +Fix: Album list refresh on wifi setting change + diff --git a/metadata/en-US/full_description.txt b/metadata/en-US/full_description.txt index 81061a5..c0ed5e6 100644 --- a/metadata/en-US/full_description.txt +++ b/metadata/en-US/full_description.txt @@ -1,4 +1,5 @@ -Make photo albums, add text and handwritten annotations. Upload them on Nextcloud server and share with friends/family. +Make photo albums, add text, video, sound and handwritten annotations. Support for photosphere image format in viewer. +Upload your albums on a Nextcloud server and share them with family and friends. To upload an album to a Nextcloud instance, you will need:
    @@ -8,11 +9,9 @@ To upload an album to a Nextcloud instance, you will need: Yet an other gallery app -Old school paper albums are not just a collection of time ordered photos, they are visuals and texts organised to tell a story, make you remember a moment in the past. +From a personal point of view, an album is not just a collection of time ordered photos, they are visuals and texts organised to tell a story, to help you remember moment and feelings from the past. -The goal of this gallery app is to mimic a full photo album though image spacial organisation, text and hand drawings, giving you the tools to express and store your memories ("souvenirs" in french). - -Well, at least trying to do it that way with my amateur android programming skills... +The goal of this gallery app is to mimic an old fashion photo album though image spacial organisation, text and hand drawings, giving you the tools to express and store your memories ("souvenirs" in french). Well, at least trying to do it that way with my amateur android programming skills... And spice it a bit with some more "modern" items inside your album, like videos or sound/music. Album modes @@ -21,7 +20,7 @@ Album are composed of several pages where you can include image or text blocs. There are two possible album mode available :
    • Tile mode : images and text blocs are organised in tiles fully covering the page. You can pan/zoom each images and handwritten annotations over them.
    • -
    • Free mode : you can resize images and blocs however you want on your page, even overlaid them. This mode need more time to design your album and generally leaves blank area that are not optimized for small smartphone.
    • +
    • Free mode : you can resize images and blocs however you want on your page, even overlaid them. This mode need more time to design your album and generally leaves blank area that are not optimized for small smartphone. (deprecated)
    Permission explained : the application need the following permissions : "INTERNET", "GET_ACCOUNTS". These permissions are used exclusively to upload/download albums from the nextcloud server and using the nextcloud account on the device. @@ -31,5 +30,6 @@ The following third party libraries are used by this app :
  • The media loading library Glide
  • The Nextcloud Single Sign On library
  • The retrofit http client
  • +
  • The photo sphere viewer javascript library
- \ No newline at end of file + diff --git a/metadata/en-US/images/phoneScreenshots/1.png b/metadata/en-US/images/phoneScreenshots/1.png index 752273d..6e1c901 100644 Binary files a/metadata/en-US/images/phoneScreenshots/1.png and b/metadata/en-US/images/phoneScreenshots/1.png differ diff --git a/metadata/en-US/images/phoneScreenshots/2.png b/metadata/en-US/images/phoneScreenshots/2.png index c536fe6..be21435 100644 Binary files a/metadata/en-US/images/phoneScreenshots/2.png and b/metadata/en-US/images/phoneScreenshots/2.png differ diff --git a/metadata/en-US/images/phoneScreenshots/3.png b/metadata/en-US/images/phoneScreenshots/3.png index 19d3d98..0527189 100644 Binary files a/metadata/en-US/images/phoneScreenshots/3.png and b/metadata/en-US/images/phoneScreenshots/3.png differ diff --git a/metadata/en-US/images/phoneScreenshots/4.png b/metadata/en-US/images/phoneScreenshots/4.png index 954b7f6..729c905 100644 Binary files a/metadata/en-US/images/phoneScreenshots/4.png and b/metadata/en-US/images/phoneScreenshots/4.png differ diff --git a/metadata/en-US/images/phoneScreenshots/5.png b/metadata/en-US/images/phoneScreenshots/5.png new file mode 100644 index 0000000..e5fd176 Binary files /dev/null and b/metadata/en-US/images/phoneScreenshots/5.png differ diff --git a/metadata/en-US/images/phoneScreenshots/6.png b/metadata/en-US/images/phoneScreenshots/6.png new file mode 100644 index 0000000..f36452e Binary files /dev/null and b/metadata/en-US/images/phoneScreenshots/6.png differ diff --git a/metadata/en-US/images/phoneScreenshots/7.png b/metadata/en-US/images/phoneScreenshots/7.png new file mode 100644 index 0000000..5dcbff1 Binary files /dev/null and b/metadata/en-US/images/phoneScreenshots/7.png differ diff --git a/metadata/en-US/short_description.txt b/metadata/en-US/short_description.txt index ca52dbf..7041da1 100644 --- a/metadata/en-US/short_description.txt +++ b/metadata/en-US/short_description.txt @@ -1 +1 @@ -Make photo albums and share them on Nextcloud +Make photo albums and share them on Nextcloud. Support for image, sound, video, photosphere and annotations. diff --git a/metadata/fr/full_description.txt b/metadata/fr/full_description.txt index 661ba39..675d18e 100644 --- a/metadata/fr/full_description.txt +++ b/metadata/fr/full_description.txt @@ -1,5 +1,5 @@ -Réalisez des albums photos, ajoutez du texte ou des annotations manuscrites. -Partagez vos albums sur un serveur Nextcloud. +Réalisez des albums photos, ajoutez du texte ou des annotations manuscrites. Possibilité d'ajouter des vidéos, sons, ou des images au format photosphere. +Téléchargerz et partagez vos albums sur un serveur Nextcloud. Pour partager un album sur une instance Nextcloud, vous aurez besoin :
    @@ -9,11 +9,9 @@ Pour partager un album sur une instance Nextcloud, vous aurez besoin : Encore une application de gallerie photo -Les albums papiers traditionnels ne sont pas juste une suite de photos ordonnées dans le temps, ce sont des supports visuels conçus pour raconter une histoire, vous rappeler un souvenir. +Les albums ne sont pas juste une suite de photos ordonnées dans le temps, ce sont des supports visuels conçus pour raconter une histoire, vous rappeler un souvenir. -L'objectif de cette application est de reproduire cet objet à travers une organisation spatiale des photos, de textes ou d'annotations manuelles, vous donnant les outils pour enregistrer des souvenirs et non une succession de photos. - -Bon, au moins essayer de le faire avec des compétences d'amateur en programmation android... +L'objectif de cette application est de reproduire cet objet traditionnel à travers une organisation spatiale des photos, de textes ou d'annotations manuelles, vous donnant les outils pour enregistrer des souvenirs et non une succession de photos. C'est du moins ce que j'essaye de faire avec des compétences d'amateur en programmation android... Style d'album @@ -22,7 +20,7 @@ Les albums sont constitués de pages sur lesquels on peut insérer des photos ou Deux styles d'album sont disponibles :
    • Le style tuile : les photo et blocs de texte sont agencés sous forme de tuiles contigues sur toute la page. Vous pouvez déplacer et zoomer les images à l'intérieur des tuiles et ajouter des annotations manuscrites.
    • -
    • Le style libre : vous pouvez redimensionner les photos et les blocs comme vous le désirez sur la page, même les superposer. Ce style vous demandera plus de temps de conception de l'ablum et laisse souvent du vide entre les éléments, ce qui n'optimise pas la lecture sur un petit smartphone.
    • +
    • Le style libre : vous pouvez redimensionner les photos et les blocs comme vous le désirez sur la page, même les superposer. Ce style vous demandera plus de temps de conception de l'ablum et laisse souvent du vide entre les éléments, ce qui n'optimise pas la lecture sur un petit smartphone. (plus supporté, deprecated)
    Explication des permissions : l'application a besoins des permissions suivantes : "INTERNET", "GET_ACCOUNTS". Ces permissions sont utilisées exclisivement pour télécharger des albums depuis le serveur Nextcloud et utiliser le compte nextcloud sur l'appareil. @@ -32,5 +30,6 @@ Cette application utilise les libraries tierces suivantes :
  • The media loading library Glide
  • The Nextcloud Single Sign On library
  • The retrofit http client
  • +
  • The photo sphere viewer javascript library
- \ No newline at end of file + diff --git a/metadata/fr/short_description.txt b/metadata/fr/short_description.txt index 9f3c35d..da0f212 100644 --- a/metadata/fr/short_description.txt +++ b/metadata/fr/short_description.txt @@ -1 +1 @@ -Conception d'album photos et partage sur Nextcloud +Conception d'album photos et partage sur Nextcloud. Supporte l'ajout de video, audio et photopshere. diff --git a/noaudio.svg b/noaudio.svg new file mode 100644 index 0000000..880b720 --- /dev/null +++ b/noaudio.svg @@ -0,0 +1,62 @@ + + + + + + image/svg+xml + + + + + + + + +