+ * Licensed under the Apache License, Version 2.0
+ */
+
+package com.danielkim.soundrecorder.fragments;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v13.app.FragmentCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.danielkim.soundrecorder.R;
+import com.danielkim.soundrecorder.ScheduledRecordingItem;
+import com.danielkim.soundrecorder.ScheduledRecordingService;
+import com.danielkim.soundrecorder.activities.AddScheduledRecordingActivity;
+import com.danielkim.soundrecorder.database.DBHelper;
+import com.danielkim.soundrecorder.didagger2.App;
+import com.github.sundeepk.compactcalendarview.CompactCalendarView;
+import com.github.sundeepk.compactcalendarview.domain.Event;
+import com.melnykov.fab.FloatingActionButton;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import javax.inject.Inject;
+
+import io.reactivex.Single;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * This Fragment shows all scheduled recordings using a CalendarView.
+ *
+ * Created by iClaude on 16/08/2017.
+ */
+
+public class ScheduledRecordingsFragment extends Fragment implements ScheduledRecordingsFragmentItemAdapter.MyOnItemClickListener {
+
+ private final String TAG = this.getClass().getSimpleName();
+ private static final String ARG_POSITION = "position";
+ private static final int REQUEST_DANGEROUS_PERMISSIONS = 0;
+ private static final int ADD_SCHEDULED_RECORDING = 0;
+ private static final int EDIT_SCHEDULED_RECORDING = 1;
+
+ private CompactCalendarView calendarView;
+ private TextView tvMonth;
+ private TextView tvDate;
+
+ private RecyclerView.Adapter adapter;
+ private List scheduledRecordings;
+ private Date selectedDate = new Date(System.currentTimeMillis());
+
+ @Inject
+ DBHelper dbHelper;
+ private final CompositeDisposable compositeDisposable = new CompositeDisposable();
+
+ public static ScheduledRecordingsFragment newInstance(int position) {
+ ScheduledRecordingsFragment f = new ScheduledRecordingsFragment();
+ Bundle b = new Bundle();
+ b.putInt(ARG_POSITION, position);
+ f.setArguments(b);
+
+ return f;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ App.getComponent().inject(this);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.fragment_scheduled_recordings, container, false);
+
+ // Title.
+ tvMonth = (TextView) v.findViewById(R.id.tvMonth);
+ String month = new SimpleDateFormat("MMMM", Locale.getDefault()).format(Calendar.getInstance().getTime());
+ tvMonth.setText(month);
+ // Calendar view.
+ calendarView = (CompactCalendarView) v.findViewById(R.id.compactcalendar_view);
+ calendarView.setListener(myCalendarViewListener);
+ // List of events for the selected day.
+ RecyclerView recyclerView = (RecyclerView) v.findViewById(R.id.rvRecordings);
+ RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
+ recyclerView.setLayoutManager(layoutManager);
+ scheduledRecordings = new ArrayList<>();
+ adapter = new ScheduledRecordingsFragmentItemAdapter(scheduledRecordings, this, recyclerView);
+ recyclerView.setAdapter(adapter);
+ // Selected day.
+ tvDate = (TextView) v.findViewById(R.id.tvDate);
+ // Add new scheduled recording button.
+ FloatingActionButton mRecordButton = (FloatingActionButton) v.findViewById(R.id.fab_add);
+ mRecordButton.setColorNormal(ContextCompat.getColor(getActivity(), R.color.primary));
+ mRecordButton.setColorPressed(ContextCompat.getColor(getActivity(), R.color.primary_dark));
+ mRecordButton.setOnClickListener(addScheduledRecordingListener);
+
+ subscribeToGetRecordingsObservable();
+
+ return v;
+ }
+
+ // Listener for the CompactCalendarView.
+ private final CompactCalendarView.CompactCalendarViewListener myCalendarViewListener = new CompactCalendarView.CompactCalendarViewListener() {
+ @Override
+ public void onDayClick(Date date) {
+ selectedDate = date;
+ displayScheduledRecordings(date);
+ }
+
+ @Override
+ public void onMonthScroll(Date date) {
+ DateFormat dateFormat = new SimpleDateFormat("MMMM", Locale.getDefault());
+ String month = dateFormat.format(date);
+ tvMonth.setText(month);
+ }
+ };
+
+ // Display the list of scheduled recordings for the selected day.
+ private void displayScheduledRecordings(Date date) {
+ List events = calendarView.getEvents(date.getTime());
+ // Put the events in a list of ScheduledRecordingItem suitable for the adapter.
+ scheduledRecordings.clear();
+ for (Event event : events) {
+ scheduledRecordings.add((ScheduledRecordingItem) event.getData());
+ }
+ Collections.sort(scheduledRecordings);
+ ((ScheduledRecordingsFragmentItemAdapter) adapter).setItems(scheduledRecordings);
+ adapter.notifyDataSetChanged();
+
+ tvDate.setText(new SimpleDateFormat("EEEE d", Locale.getDefault()).format(date));
+ }
+
+ // Get all scheduled recordings and provide them.
+ private final Single> getRecordingsSingle = Single.create(emitter -> {
+ try {
+ List recordings = dbHelper.getAllScheduledRecordings();
+ emitter.onSuccess(recordings);
+ } catch (Exception e) {
+ emitter.onError(e);
+ }
+ });
+
+ private void subscribeToGetRecordingsObservable() {
+ Disposable subscribe = getRecordingsSingle
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ this::updateUI,
+ throwable -> Log.e(TAG, throwable.getMessage())
+ );
+ compositeDisposable.add(subscribe);
+ }
+
+ // Update the UI with the list of scheduled recordings.
+ private void updateUI(List scheduledRecordings) {
+ calendarView.removeAllEvents();
+ for (ScheduledRecordingItem item : scheduledRecordings) {
+ Event event = new Event(ContextCompat.getColor(getActivity(), R.color.accent), item.getStart(), item);
+ calendarView.addEvent(event, false);
+ }
+ calendarView.invalidate(); // refresh the calendar view
+ myCalendarViewListener.onDayClick(selectedDate); // click to show current day
+ }
+
+ // Click listener for the elements of the RecyclerView (for editing scheduled recordings).
+ @Override
+ public void onItemClick(ScheduledRecordingItem item) {
+ Intent intent = AddScheduledRecordingActivity.makeIntent(getActivity(), item);
+ startActivityForResult(intent, EDIT_SCHEDULED_RECORDING);
+ }
+
+ // Long click listener for the elements of the RecyclerView (for deleting or renaming scheduled recordings).
+ @Override
+ public void onItemLongClick(ScheduledRecordingItem item) {
+ // Item delete confirm
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(getString(R.string.dialog_title_delete));
+ builder.setMessage(R.string.dialog_text_delete_generic);
+ builder.setPositiveButton(R.string.dialog_action_ok,
+ (dialogInterface, i) -> {
+ final Single deleteItemSingle = Single.create(emitter -> {
+ int numDeleted = dbHelper.removeScheduledRecording(item.getId());
+ emitter.onSuccess(numDeleted);
+ });
+ Disposable subscribe = deleteItemSingle
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::deleteItemCompleted);
+ compositeDisposable.add(subscribe);
+ });
+ builder.setCancelable(true);
+ builder.setNegativeButton(getString(R.string.dialog_action_cancel),
+ (dialog, id) -> dialog.cancel());
+
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+
+ // The item has just been deleted.
+ private void deleteItemCompleted(int numDeleted) {
+ Activity activity = getActivity();
+ if (numDeleted > 0) {
+ Toast.makeText(activity, activity.getString(R.string.toast_scheduledrecording_deleted), Toast.LENGTH_SHORT).show();
+ subscribeToGetRecordingsObservable();
+ activity.startService(ScheduledRecordingService.makeIntent(activity, false));
+ } else {
+ Toast.makeText(activity, activity.getString(R.string.toast_scheduledrecording_deleted_error), Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ // Click listener of the button to add a new scheduled recording.
+ private final View.OnClickListener addScheduledRecordingListener = view -> checkPermissions();
+
+ // Check dangerous permissions for Android Marshmallow+.
+ @SuppressWarnings("ConstantConditions")
+ private void checkPermissions() {
+ // Check permissions.
+ boolean writePerm = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+ boolean audioPerm = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
+ String[] arrPermissions;
+ if (!writePerm && !audioPerm) {
+ arrPermissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO};
+ } else if (!writePerm && audioPerm) {
+ arrPermissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
+ } else if (writePerm && !audioPerm) {
+ arrPermissions = new String[]{Manifest.permission.RECORD_AUDIO};
+ } else {
+ startAddScheduledRecordingActivity();
+ return;
+ }
+
+ // Request permissions.
+ FragmentCompat.requestPermissions(this, arrPermissions, REQUEST_DANGEROUS_PERMISSIONS);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ boolean granted = true;
+ for (int grantResult : grantResults) { // we nee all permissions granted
+ if (grantResult != PackageManager.PERMISSION_GRANTED)
+ granted = false;
+ }
+
+ if (granted)
+ startAddScheduledRecordingActivity();
+ else
+ Toast.makeText(getActivity(), getString(R.string.toast_permissions_denied), Toast.LENGTH_LONG).show();
+ }
+
+ private void startAddScheduledRecordingActivity() {
+ Intent intent = AddScheduledRecordingActivity.makeIntent(getActivity(), selectedDate.getTime());
+ startActivityForResult(intent, ADD_SCHEDULED_RECORDING);
+ }
+
+ // After a new scheduled recording has been added, get all the recordings and refresh the layout.
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if ((requestCode == ADD_SCHEDULED_RECORDING || requestCode == EDIT_SCHEDULED_RECORDING) && resultCode == Activity.RESULT_OK) {
+ subscribeToGetRecordingsObservable();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ if (!compositeDisposable.isDisposed())
+ compositeDisposable.dispose();
+ }
+}
diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java
new file mode 100644
index 00000000..78433c30
--- /dev/null
+++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/ScheduledRecordingsFragmentItemAdapter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2017 Claudio "iClaude" Agostini
+ * Licensed under the Apache License, Version 2.0
+ */
+
+package com.danielkim.soundrecorder.fragments;
+
+import android.graphics.Color;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.danielkim.soundrecorder.R;
+import com.danielkim.soundrecorder.ScheduledRecordingItem;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Adapter of the RecyclerView within ScheduledRecordingsFragment.
+ */
+
+public class ScheduledRecordingsFragmentItemAdapter extends RecyclerView.Adapter {
+
+ public interface MyOnItemClickListener {
+ void onItemClick(ScheduledRecordingItem item);
+
+ void onItemLongClick(ScheduledRecordingItem item);
+ }
+
+
+ // ViewHolder.
+ public class ItemViewHolder extends RecyclerView.ViewHolder {
+ private final TextView tvStart;
+ private final TextView tvEnd;
+ private final TextView tvColor;
+
+ public ItemViewHolder(View v) {
+ super(v);
+ tvStart = (TextView) v.findViewById(R.id.tvStart);
+ tvEnd = (TextView) v.findViewById(R.id.tvEnd);
+ tvColor = (TextView) v.findViewById(R.id.tvColor);
+
+ v.setOnClickListener(view -> {
+ int pos = recyclerView.getChildAdapterPosition(view);
+ if (pos >= 0 && pos < getItemCount()) {
+ listener.onItemClick(items.get(pos));
+ }
+ });
+
+ v.setOnLongClickListener(view -> {
+ int pos = recyclerView.getChildAdapterPosition(view);
+ if (pos >= 0 && pos < getItemCount()) {
+ listener.onItemLongClick(items.get(pos));
+ }
+ return true;
+ });
+ }
+ }
+
+ // Adapter.
+ private List items;
+ private final MyOnItemClickListener listener;
+ private final RecyclerView recyclerView;
+ private final int[] colors = {Color.argb(255, 255, 193, 7),
+ Color.argb(255, 244, 67, 54), Color.argb(255, 99, 233, 112),
+ Color.argb(255, 7, 168, 255), Color.argb(255, 255, 7, 251),
+ Color.argb(255, 255, 61, 7), Color.argb(255, 205, 7, 255)};
+
+ public ScheduledRecordingsFragmentItemAdapter(List items, MyOnItemClickListener listener, RecyclerView recyclerView) {
+ this.items = items;
+ this.recyclerView = recyclerView;
+ this.listener = listener;
+ }
+
+ public void setItems(List items) {
+ this.items = items;
+ }
+
+ @Override
+ public int getItemCount() {
+ return items.size();
+ }
+
+ @Override
+ public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
+ View view = LayoutInflater
+ .from(viewGroup.getContext())
+ .inflate(R.layout.fragment_scheduled_recordings_item, viewGroup, false);
+
+ return new ItemViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(ItemViewHolder holder, int position) {
+ ScheduledRecordingItem item = items.get(position);
+ if (item == null) return;
+ DateFormat dateFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
+ holder.tvStart.setText(dateFormat.format(new Date(item.getStart())));
+ holder.tvEnd.setText(dateFormat.format(new Date(item.getEnd())));
+
+ int posCol = position % (colors.length);
+ holder.tvColor.setBackgroundColor(colors[posCol]);
+ }
+}
diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/SettingsFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/SettingsFragment.java
index fc0d47f0..624e128e 100644
--- a/app/src/main/java/com/danielkim/soundrecorder/fragments/SettingsFragment.java
+++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/SettingsFragment.java
@@ -37,7 +37,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
@Override
public boolean onPreferenceClick(Preference preference) {
LicensesFragment licensesFragment = new LicensesFragment();
- licensesFragment.show(((SettingsActivity)getActivity()).getSupportFragmentManager().beginTransaction(), "dialog_licenses");
+ licensesFragment.show(((SettingsActivity) getActivity()).getFragmentManager().beginTransaction(), "dialog_licenses");
return true;
}
});
diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java
new file mode 100644
index 00000000..c579b4dd
--- /dev/null
+++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/TimePickerFragment.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2017 Claudio "iClaude" Agostini
+ * Licensed under the Apache License, Version 2.0
+ */
+
+package com.danielkim.soundrecorder.fragments;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.TimePickerDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.format.DateFormat;
+import android.widget.TimePicker;
+
+/**
+ * Shows a dialog to pick a time (hour and minute).
+ * Communicates the time selected through an interface.
+ * This class stores and communicates the id of the view that needs the time, so it can be used
+ * for different views within the same Activity/Fragment.
+ */
+
+public class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener {
+ private static final String ARG_VIEW_ID = "ARG_VIEW_ID";
+ private static final String ARG_HOUR = "ARG_HOUR";
+ private static final String ARG_MINUTE = "ARG_MINUTE";
+
+
+ private MyOnTimeSetListener listener;
+
+ public static TimePickerFragment newInstance(long viewId, int hour, int minute) {
+ TimePickerFragment f = new TimePickerFragment();
+ Bundle bundle = new Bundle();
+ bundle.putLong(ARG_VIEW_ID, viewId);
+ bundle.putInt(ARG_HOUR, hour);
+ bundle.putInt(ARG_MINUTE, minute);
+ f.setArguments(bundle);
+
+ return f;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ int hour = getArguments().getInt(ARG_HOUR);
+ int minute = getArguments().getInt(ARG_MINUTE);
+
+ return new TimePickerDialog(getActivity(), this, hour, minute, DateFormat.is24HourFormat(getActivity()));
+ }
+
+ @TargetApi(23)
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ try {
+ listener = (TimePickerFragment.MyOnTimeSetListener) context;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(context.toString() + "must implement TimePickerFragment.MyOnTimeSetListener");
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ try {
+ listener = (TimePickerFragment.MyOnTimeSetListener) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + "must implement TimePickerFragment.MyOnTimeSetListener");
+ }
+ }
+
+ @Override
+ public void onTimeSet(TimePicker timePicker, int hourOfDay, int minute) {
+ if (listener != null) {
+ listener.onTimeSet(getArguments().getLong(ARG_VIEW_ID, 0), hourOfDay, minute);
+ }
+ }
+
+ // Interface form communication with the Activity.
+ public interface MyOnTimeSetListener {
+ void onTimeSet(long viewId, int hour, int minute);
+ }
+
+}
diff --git a/app/src/main/java/com/danielkim/soundrecorder/utils/Utils.java b/app/src/main/java/com/danielkim/soundrecorder/utils/Utils.java
new file mode 100644
index 00000000..a0c1f797
--- /dev/null
+++ b/app/src/main/java/com/danielkim/soundrecorder/utils/Utils.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2017 Claudio "iClaude" Agostini
+ * Licensed under the Apache License, Version 2.0
+ */
+
+package com.danielkim.soundrecorder.utils;
+
+import android.content.Context;
+import android.os.Environment;
+
+import java.io.File;
+
+/**
+ * Common utility methods.
+ */
+
+public class Utils {
+
+ /*
+ Get, or create if necessary, the path of the directory where to save recordings.
+ */
+ public static String getDirectoryPath(Context context) {
+ String directoryPath;
+
+ if (isExternalStorageWritable()) {
+ directoryPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/SoundRecorder";
+ File f = new File(directoryPath);
+ boolean available = f.mkdirs() || f.isDirectory();
+ if (available)
+ return directoryPath;
+ }
+
+ return context.getFilesDir().getAbsolutePath();
+ }
+
+ private static boolean isExternalStorageWritable() {
+ String state = Environment.getExternalStorageState();
+ return Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
+ }
+}
diff --git a/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png
new file mode 100644
index 00000000..694179bd
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png
new file mode 100644
index 00000000..3856041d
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png differ
diff --git a/app/src/main/res/drawable-nodpi/example_picture.png b/app/src/main/res/drawable-nodpi/example_picture.png
new file mode 100644
index 00000000..e0627f53
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/example_picture.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png
new file mode 100644
index 00000000..67bb598e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png
new file mode 100644
index 00000000..0fdced8f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png differ
diff --git a/app/src/main/res/drawable/ic_access_time_black_24dp.xml b/app/src/main/res/drawable/ic_access_time_black_24dp.xml
new file mode 100644
index 00000000..60b9f126
--- /dev/null
+++ b/app/src/main/res/drawable/ic_access_time_black_24dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/record_progress_bar.xml b/app/src/main/res/drawable/record_progress_bar.xml
index 59892fd7..818195bd 100644
--- a/app/src/main/res/drawable/record_progress_bar.xml
+++ b/app/src/main/res/drawable/record_progress_bar.xml
@@ -4,4 +4,5 @@
android:shape="oval">
+
diff --git a/app/src/main/res/drawable/record_progress_bar_background.xml b/app/src/main/res/drawable/record_progress_bar_background.xml
index fb9906b8..0a893be9 100644
--- a/app/src/main/res/drawable/record_progress_bar_background.xml
+++ b/app/src/main/res/drawable/record_progress_bar_background.xml
@@ -4,4 +4,5 @@
android:shape="oval">
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_add_scheduled_recording.xml b/app/src/main/res/layout/activity_add_scheduled_recording.xml
new file mode 100644
index 00000000..35de5db2
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_scheduled_recording.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_preferences.xml b/app/src/main/res/layout/activity_preferences.xml
index 8082627b..d0e6e34b 100644
--- a/app/src/main/res/layout/activity_preferences.xml
+++ b/app/src/main/res/layout/activity_preferences.xml
@@ -1,9 +1,11 @@
+
+ layout="@layout/toolbar" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/card_view.xml b/app/src/main/res/layout/card_view.xml
index 0ec48c46..e4cbd8b4 100644
--- a/app/src/main/res/layout/card_view.xml
+++ b/app/src/main/res/layout/card_view.xml
@@ -1,7 +1,8 @@
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ android:layout_marginRight="7dp"
+ android:src="@drawable/ic_fileviewer" />
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical">
+ android:textStyle="bold" />
+ android:text="00:00"
+ android:textSize="12sp" />
+ android:textSize="12sp" />
diff --git a/app/src/main/res/layout/fragment_licenses.xml b/app/src/main/res/layout/fragment_licenses.xml
index 40945993..6ec7b0a3 100644
--- a/app/src/main/res/layout/fragment_licenses.xml
+++ b/app/src/main/res/layout/fragment_licenses.xml
@@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
- android:text="@string/link"/>
+ android:text="@string/link" />
-
+ android:layout_marginBottom="64dp"
+ android:fontFamily="sans-serif-light"
+ android:text="00:00"
+ android:textColor="@color/primary_text"
+ android:textSize="60sp" />
diff --git a/app/src/main/res/layout/fragment_scheduled_recordings.xml b/app/src/main/res/layout/fragment_scheduled_recordings.xml
new file mode 100644
index 00000000..e2ad0582
--- /dev/null
+++ b/app/src/main/res/layout/fragment_scheduled_recordings.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_scheduled_recordings_item.xml b/app/src/main/res/layout/fragment_scheduled_recordings_item.xml
new file mode 100644
index 00000000..cb42741a
--- /dev/null
+++ b/app/src/main/res/layout/fragment_scheduled_recordings_item.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_addscheduledrecording.xml b/app/src/main/res/menu/menu_addscheduledrecording.xml
new file mode 100644
index 00000000..d806d7fd
--- /dev/null
+++ b/app/src/main/res/menu/menu_addscheduledrecording.xml
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
index bbf51a05..d1c27f5c 100644
--- a/app/src/main/res/menu/menu_main.xml
+++ b/app/src/main/res/menu/menu_main.xml
@@ -1,6 +1,4 @@
-