diff --git a/app/src/main/java/org/linphone/LinphoneActivity.java b/app/src/main/java/org/linphone/LinphoneActivity.java index 4de17425a..ab94f84e1 100644 --- a/app/src/main/java/org/linphone/LinphoneActivity.java +++ b/app/src/main/java/org/linphone/LinphoneActivity.java @@ -110,6 +110,7 @@ import org.linphone.purchase.InAppPurchaseActivity; import org.linphone.views.AddressText; import org.linphone.utils.LinphoneGenericActivity; import org.linphone.utils.LinphoneUtils; +import org.linphone.recording.RecordingListFragment; import org.linphone.xmlrpc.XmlRpcHelper; import org.linphone.xmlrpc.XmlRpcListenerBase; @@ -135,7 +136,7 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick private StatusFragment statusFragment; private TextView missedCalls, missedChats; private RelativeLayout contacts, history, dialer, chat; - private View contacts_selected, history_selected, dialer_selected, chat_selected; + private View contacts_selected, history_selected, dialer_selected, chat_selected, record_selected; private LinearLayout mTopBar; private TextView mTopBarTitle; private ImageView cancel; @@ -428,6 +429,9 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick case CONTACT_DEVICES: fragment = new DevicesFragment(); break; + case RECORDING_LIST: + fragment = new RecordingListFragment(); + break; default: break; } @@ -638,7 +642,7 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick } public void displayRecordings() { - + changeCurrentFragment(FragmentsAvailable.RECORDING_LIST, null); } public void displayContactsForEdition(String sipAddress, String displayName) { @@ -1634,7 +1638,7 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick LinphoneActivity.instance().displayInapp(); } } - if (sideMenuItemList.getAdapter().getItem(i).toString().equals(R.string.menu_recordings)) { + if (sideMenuItemList.getAdapter().getItem(i).toString().equals(getString(R.string.menu_recordings))) { LinphoneActivity.instance().displayRecordings(); } openOrCloseSideMenu(false); diff --git a/app/src/main/java/org/linphone/fragments/FragmentsAvailable.java b/app/src/main/java/org/linphone/fragments/FragmentsAvailable.java index d59fc5df0..9ed1af3da 100644 --- a/app/src/main/java/org/linphone/fragments/FragmentsAvailable.java +++ b/app/src/main/java/org/linphone/fragments/FragmentsAvailable.java @@ -37,7 +37,8 @@ public enum FragmentsAvailable { INFO_GROUP_CHAT, GROUP_CHAT, MESSAGE_IMDN, - CONTACT_DEVICES; + CONTACT_DEVICES, + RECORDING_LIST; public boolean shouldAddItselfToTheRightOf(FragmentsAvailable fragment) { switch (this) { diff --git a/app/src/main/java/org/linphone/recording/Recording.java b/app/src/main/java/org/linphone/recording/Recording.java new file mode 100644 index 000000000..588360772 --- /dev/null +++ b/app/src/main/java/org/linphone/recording/Recording.java @@ -0,0 +1,152 @@ +package org.linphone.recording; + +/* +Recording.java +Copyright (C) 2018 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import android.annotation.SuppressLint; +import android.support.annotation.NonNull; + +import org.linphone.LinphoneManager; +import org.linphone.core.Player; +import org.linphone.core.PlayerListener; +import org.linphone.mediastream.Log; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Timer; +import java.util.TimerTask; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Recording implements PlayerListener, Comparable { + private String recordPath, name; + private Date recordDate; + private Player player; + private RecordingListener listener; + private Timer timer; + private TimerTask updateCurrentPositionTimer; + + private static final Pattern mRecordPattern = Pattern.compile(".*/(.*)_(\\d{2}-\\d{2}-\\d{4}-\\d{2}-\\d{2}-\\d{2})\\..*"); + + @SuppressLint("SimpleDateFormat") + public Recording(String recordPath) { + this.recordPath = recordPath; + + Matcher m = mRecordPattern.matcher(recordPath); + if (m.matches()) { + name = m.group(1); + + try { + recordDate = new SimpleDateFormat("dd-MM-yyyy-HH-mm-ss").parse(m.group(2)); + } catch (ParseException e) { + Log.e(e); + } + } + + timer = new Timer(); + updateCurrentPositionTimer = new TimerTask() { + @Override + public void run() { + if (listener != null) listener.currentPositionChanged(getCurrentPosition()); + } + }; + + player = LinphoneManager.getLc().createLocalPlayer(null, null, null); + player.setListener(this); + player.open(recordPath); + } + + public String getRecordPath() { + return recordPath; + } + + public String getName() { + return name; + } + + public Date getRecordDate() { + return recordDate; + } + + public boolean isClosed() { + return player.getState() == Player.State.Closed; + } + + public void play() { + player.start(); + //timer.scheduleAtFixedRate(updateCurrentPositionTimer, 0, 1000); + } + + public boolean isPlaying() { + return player.getState() == Player.State.Playing; + } + + public void pause() { + if (!isClosed()) { + player.pause(); + + timer.cancel(); + timer.purge(); + } + } + + public boolean isPaused() { + return player.getState() == Player.State.Paused; + } + + public void seek(int i) { + if (!isClosed()) player.seek(i); + } + + public int getCurrentPosition() { + return player.getCurrentPosition(); + } + + public int getDuration() { + return player.getDuration(); + } + + public void close() { + player.close(); + } + + public void setRecordingListener(RecordingListener listener) { + this.listener = listener; + } + + @Override + public void onEofReached(Player player) { + if (listener != null) listener.endOfRecordReached(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Recording) { + Recording r = (Recording) o; + return recordPath.equals(r.getRecordPath()); + } + return false; + } + + @Override + public int compareTo(@NonNull Recording o) { + return -recordDate.compareTo(o.getRecordDate()); + } +} diff --git a/app/src/main/java/org/linphone/recording/RecordingAdapter.java b/app/src/main/java/org/linphone/recording/RecordingAdapter.java new file mode 100644 index 000000000..5222b3ef0 --- /dev/null +++ b/app/src/main/java/org/linphone/recording/RecordingAdapter.java @@ -0,0 +1,275 @@ +package org.linphone.recording; + +/* +RecordingAdapter.java +Copyright (C) 2018 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +import org.linphone.R; +import org.linphone.mediastream.Log; +import org.linphone.ui.SelectableAdapter; +import org.linphone.ui.SelectableHelper; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +public class RecordingAdapter extends SelectableAdapter { + + public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { + public ImageView playButton; + public TextView name, date, currentPosition, duration; + public SeekBar progressionBar; + public CheckBox select; + public LinearLayout separator; + public TextView separatorText; + private RecordingAdapter.ViewHolder.ClickListener listener; + + public ViewHolder(View view, RecordingAdapter.ViewHolder.ClickListener listener) { + super(view); + + playButton = view.findViewById(R.id.record_play); + name = view.findViewById(R.id.record_name); + date = view.findViewById(R.id.record_date); + currentPosition = view.findViewById(R.id.record_current_time); + duration = view.findViewById(R.id.record_duration); + progressionBar = view.findViewById(R.id.record_progression_bar); + select = view.findViewById(R.id.delete); + separator = view.findViewById(R.id.separator); + separatorText = view.findViewById(R.id.separator_text); + + this.listener = listener; + view.setOnClickListener(this); + view.setOnLongClickListener(this); + } + + @Override + public void onClick(View view) { + if (listener != null) { + listener.onItemClicked(getAdapterPosition()); + } + } + + @Override + public boolean onLongClick(View view) { + if (listener != null) { + return listener.onItemLongClicked(getAdapterPosition()); + } + return false; + } + + public interface ClickListener { + void onItemClicked(int position); + + boolean onItemLongClicked(int position); + } + } + + private List recordings; + private Context context; + private RecordingAdapter.ViewHolder.ClickListener clickListener; + + public RecordingAdapter(Context context, List recordings, RecordingAdapter.ViewHolder.ClickListener listener, SelectableHelper helper) { + super(helper); + + this.recordings = recordings; + this.context = context; + this.clickListener = listener; + } + + @Override + public Object getItem(int position) { + return recordings.get(position); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recording_cell, viewGroup, false); + return new RecordingAdapter.ViewHolder(v, clickListener); + } + + @SuppressLint("SimpleDateFormat") + @Override + public void onBindViewHolder(@NonNull final ViewHolder viewHolder, int i) { + final Recording record = recordings.get(i); + + viewHolder.name.setSelected(true); // For automated horizontal scrolling of long texts + + Calendar recordTime = Calendar.getInstance(); + recordTime.setTime(record.getRecordDate()); + viewHolder.separatorText.setText(DateToHumanDate(recordTime)); + viewHolder.select.setVisibility(isEditionEnabled() ? View.VISIBLE : View.GONE); + viewHolder.select.setChecked(isSelected(i)); + + if (i > 0) { + Recording previousRecord = recordings.get(i - 1); + Date previousRecordDate = previousRecord.getRecordDate(); + Calendar previousRecordTime = Calendar.getInstance(); + previousRecordTime.setTime(previousRecordDate); + + if (isSameDay(previousRecordTime, recordTime)) { + viewHolder.separator.setVisibility(View.GONE); + } else { + viewHolder.separator.setVisibility(View.VISIBLE); + } + } else { + viewHolder.separator.setVisibility(View.VISIBLE); + } + + if (record.isPlaying()) { + viewHolder.playButton.setImageResource(R.drawable.record_pause); + } else { + viewHolder.playButton.setImageResource(R.drawable.record_play); + } + viewHolder.playButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (record.isPaused()) { + record.play(); + viewHolder.playButton.setImageResource(R.drawable.record_pause); + } else { + record.pause(); + viewHolder.playButton.setImageResource(R.drawable.record_play); + } + } + }); + + viewHolder.name.setText(record.getName()); + viewHolder.date.setText(new SimpleDateFormat("HH:mm").format(record.getRecordDate())); + + int position = record.getCurrentPosition(); + viewHolder.currentPosition.setText(String.format("%02d:%02d", + TimeUnit.MILLISECONDS.toMinutes(position), + TimeUnit.MILLISECONDS.toSeconds(position) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(position)) + )); + //viewHolder.currentPosition.setText("00:00"); + + int duration = record.getDuration(); + viewHolder.duration.setText(String.format("%02d:%02d", + TimeUnit.MILLISECONDS.toMinutes(duration), + TimeUnit.MILLISECONDS.toSeconds(duration) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration)) + )); + + viewHolder.progressionBar.setMax(100); + viewHolder.progressionBar.setProgress(0); + viewHolder.progressionBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + if (progress == 0) { + record.seek(0); + } else if (progress == seekBar.getMax()) { + if (record.isPlaying()) record.pause(); + record.seek(0); + seekBar.setProgress(0); + } else { + record.seek(progress); + + int currentPosition = record.getCurrentPosition(); + viewHolder.currentPosition.setText(String.format("%02d:%02d", + TimeUnit.MILLISECONDS.toMinutes(currentPosition), + TimeUnit.MILLISECONDS.toSeconds(currentPosition) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(currentPosition)) + )); + } + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + record.setRecordingListener(new RecordingListener() { + @Override + public void currentPositionChanged(int currentPosition) { + viewHolder.currentPosition.setText(String.format("%02d:%02", + currentPosition % 60, + currentPosition - (currentPosition % 60) * 60, Locale.getDefault())); + viewHolder.progressionBar.setProgress(currentPosition); + } + + @Override + public void endOfRecordReached() { + record.pause(); + record.seek(0); + viewHolder.progressionBar.setProgress(0); + } + }); + } + + @Override + public int getItemCount() { + return recordings.size(); + } + + @SuppressLint("SimpleDateFormat") + private String DateToHumanDate(Calendar cal) { + SimpleDateFormat dateFormat; + if (isToday(cal)) { + return context.getString(R.string.today); + } else if (isYesterday(cal)) { + return context.getString(R.string.yesterday); + } else { + dateFormat = new SimpleDateFormat(context.getResources().getString(R.string.history_date_format)); + } + + return dateFormat.format(cal.getTime()); + } + + private boolean isSameDay(Calendar cal1, Calendar cal2) { + if (cal1 == null || cal2 == null) { + return false; + } + + return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) && + cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && + cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR)); + } + + private boolean isToday(Calendar cal) { + return isSameDay(cal, Calendar.getInstance()); + } + + private boolean isYesterday(Calendar cal) { + Calendar yesterday = Calendar.getInstance(); + yesterday.roll(Calendar.DAY_OF_MONTH, -1); + return isSameDay(cal, yesterday); + } +} diff --git a/app/src/main/java/org/linphone/recording/RecordingListFragment.java b/app/src/main/java/org/linphone/recording/RecordingListFragment.java new file mode 100644 index 000000000..0a17aa3c7 --- /dev/null +++ b/app/src/main/java/org/linphone/recording/RecordingListFragment.java @@ -0,0 +1,192 @@ +package org.linphone.recording; + +/* +RecordingListFragment.java +Copyright (C) 2018 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.TextView; + +import org.linphone.LinphoneUtils; +import org.linphone.R; +import org.linphone.activities.LinphoneActivity; +import org.linphone.fragments.FragmentsAvailable; +import org.linphone.mediastream.Log; +import org.linphone.ui.SelectableHelper; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RecordingListFragment extends Fragment implements View.OnClickListener, AdapterView.OnItemClickListener, RecordingAdapter.ViewHolder.ClickListener, SelectableHelper.DeleteListener { + private RecyclerView recordingList; + private List recordings; + private TextView noRecordings; + private RecordingAdapter recordingAdapter; + private LinearLayoutManager layoutManager; + private Context context; + private SelectableHelper selectableHelper; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.recordings, container, false); + + context = getActivity().getApplicationContext(); + selectableHelper = new SelectableHelper(view, this); + + recordingList = view.findViewById(R.id.recording_list); + noRecordings = view.findViewById(R.id.no_recordings); + + layoutManager = new LinearLayoutManager(context); + recordingList.setLayoutManager(layoutManager); + + //Divider between items + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recordingList.getContext(), + layoutManager.getOrientation()); + dividerItemDecoration.setDrawable(context.getResources().getDrawable(R.drawable.divider)); + recordingList.addItemDecoration(dividerItemDecoration); + + recordings = new ArrayList<>(); + + return view; + } + + private void hideRecordingListAndDisplayMessageIfEmpty() { + if (recordings == null || recordings.isEmpty()) { + noRecordings.setVisibility(View.VISIBLE); + recordingList.setVisibility(View.GONE); + } else { + noRecordings.setVisibility(View.GONE); + recordingList.setVisibility(View.VISIBLE); + } + } + + public void searchForRecordings() { + String recordingsDirectory = LinphoneUtils.getRecordingsDirectory(context); + File directory = new File(recordingsDirectory); + + if (directory.exists() && directory.isDirectory()) { + File[] existingRecordings = directory.listFiles(); + + for (File f : existingRecordings) { + boolean exists = false; + for(Recording r : recordings) { + if (r.getRecordPath().equals(f.getPath())) { + exists = true; + break; + } + } + + if (!exists) recordings.add(new Recording(f.getPath())); + } + + Collections.sort(recordings); + } + } + + @Override + public void onResume() { + super.onResume(); + + if (LinphoneActivity.isInstanciated()) { + LinphoneActivity.instance().selectMenu(FragmentsAvailable.RECORDING_LIST); + } + + searchForRecordings(); + + hideRecordingListAndDisplayMessageIfEmpty(); + recordingAdapter = new RecordingAdapter(getActivity().getApplicationContext(), recordings, this, selectableHelper); + recordingList.setAdapter(recordingAdapter); + selectableHelper.setAdapter(recordingAdapter); + selectableHelper.setDialogMessage(R.string.recordings_delete_dialog); + } + + @Override + public void onPause() { + super.onPause(); + + // Close all opened recordings + for (Recording r : recordings) { + if (!r.isClosed()) { + if (r.isPlaying()) r.pause(); + r.close(); + } + } + } + + @Override + public void onClick(View v) { + + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (recordingAdapter.isEditionEnabled()) { + Recording record = recordings.get(position); + recordings.remove(position); + + if (record.isPlaying()) record.pause(); + record.close(); + + File recordingFile = new File(record.getRecordPath()); + recordingFile.delete(); + } + } + + @Override + public void onItemClicked(int position) { + if (recordingAdapter.isEditionEnabled()) { + recordingAdapter.toggleSelection(position); + } + } + + @Override + public boolean onItemLongClicked(int position) { + if (!recordingAdapter.isEditionEnabled()) { + selectableHelper.enterEditionMode(); + } + recordingAdapter.toggleSelection(position); + return true; + } + + @Override + public void onDeleteSelection(Object[] objectsToDelete) { + int size = recordingAdapter.getSelectedItemCount(); + for (int i = 0; i < size; i++) { + Recording record = (Recording) objectsToDelete[i]; + recordings.remove(record); + + if (record.isPlaying()) record.pause(); + record.close(); + + File recordingFile = new File(record.getRecordPath()); + recordingFile.delete(); + } + } +} diff --git a/app/src/main/java/org/linphone/recording/RecordingListener.java b/app/src/main/java/org/linphone/recording/RecordingListener.java new file mode 100644 index 000000000..8853d8c14 --- /dev/null +++ b/app/src/main/java/org/linphone/recording/RecordingListener.java @@ -0,0 +1,26 @@ +package org.linphone.recording; + +/* +RecordingListener.java +Copyright (C) 2018 Belledonne Communications, Grenoble, France + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +public interface RecordingListener { + void currentPositionChanged(int currentPosition); + + void endOfRecordReached(); +} diff --git a/app/src/main/res/drawable/menu_recordings.png b/app/src/main/res/drawable-xhdpi/menu_recordings.png similarity index 100% rename from app/src/main/res/drawable/menu_recordings.png rename to app/src/main/res/drawable-xhdpi/menu_recordings.png diff --git a/app/src/main/res/drawable/options_rec_default.png b/app/src/main/res/drawable-xhdpi/options_rec_default.png similarity index 100% rename from app/src/main/res/drawable/options_rec_default.png rename to app/src/main/res/drawable-xhdpi/options_rec_default.png diff --git a/app/src/main/res/drawable/options_rec_selected.png b/app/src/main/res/drawable-xhdpi/options_rec_selected.png similarity index 100% rename from app/src/main/res/drawable/options_rec_selected.png rename to app/src/main/res/drawable-xhdpi/options_rec_selected.png diff --git a/app/src/main/res/drawable-xhdpi/record_pause.png b/app/src/main/res/drawable-xhdpi/record_pause.png new file mode 100644 index 000000000..3b9311e43 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/record_pause.png differ diff --git a/app/src/main/res/drawable-xhdpi/record_play.png b/app/src/main/res/drawable-xhdpi/record_play.png new file mode 100644 index 000000000..486dfb0de Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/record_play.png differ diff --git a/app/src/main/res/drawable/recording.png b/app/src/main/res/drawable-xhdpi/recording.png similarity index 100% rename from app/src/main/res/drawable/recording.png rename to app/src/main/res/drawable-xhdpi/recording.png diff --git a/app/src/main/res/layout/recording_cell.xml b/app/src/main/res/layout/recording_cell.xml new file mode 100644 index 000000000..f3a477de8 --- /dev/null +++ b/app/src/main/res/layout/recording_cell.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recordings.xml b/app/src/main/res/layout/recordings.xml new file mode 100644 index 000000000..a76d1297d --- /dev/null +++ b/app/src/main/res/layout/recordings.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d7ad50480..747cd551b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -289,6 +289,7 @@ No recordings. + Do you really want to delete and leave the selected recordings? Send log