AntennaPod Echo (#6780)
This commit is contained in:
parent
637230e382
commit
ee554d0306
@ -89,6 +89,7 @@ dependencies {
|
|||||||
implementation project(':storage:preferences')
|
implementation project(':storage:preferences')
|
||||||
implementation project(':ui:app-start-intent')
|
implementation project(':ui:app-start-intent')
|
||||||
implementation project(':ui:common')
|
implementation project(':ui:common')
|
||||||
|
implementation project(':ui:echo')
|
||||||
implementation project(':ui:glide')
|
implementation project(':ui:glide')
|
||||||
implementation project(':ui:i18n')
|
implementation project(':ui:i18n')
|
||||||
implementation project(':ui:statistics')
|
implementation project(':ui:statistics')
|
||||||
|
@ -21,12 +21,14 @@ import androidx.core.content.ContextCompat;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentContainerView;
|
import androidx.fragment.app.FragmentContainerView;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.ui.home.sections.EchoSection;
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
@ -60,6 +62,7 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
|
|||||||
public static final String PREF_NAME = "PrefHomeFragment";
|
public static final String PREF_NAME = "PrefHomeFragment";
|
||||||
public static final String PREF_HIDDEN_SECTIONS = "PrefHomeSectionsString";
|
public static final String PREF_HIDDEN_SECTIONS = "PrefHomeSectionsString";
|
||||||
public static final String PREF_DISABLE_NOTIFICATION_PERMISSION_NAG = "DisableNotificationPermissionNag";
|
public static final String PREF_DISABLE_NOTIFICATION_PERMISSION_NAG = "DisableNotificationPermissionNag";
|
||||||
|
public static final String PREF_HIDE_ECHO = "HideEcho";
|
||||||
|
|
||||||
private static final String KEY_UP_ARROW = "up_arrow";
|
private static final String KEY_UP_ARROW = "up_arrow";
|
||||||
private boolean displayUpArrow;
|
private boolean displayUpArrow;
|
||||||
@ -94,13 +97,19 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
|
|||||||
private void populateSectionList() {
|
private void populateSectionList() {
|
||||||
viewBinding.homeContainer.removeAllViews();
|
viewBinding.homeContainer.removeAllViews();
|
||||||
|
|
||||||
|
SharedPreferences prefs = getContext().getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE);
|
||||||
if (Build.VERSION.SDK_INT >= 33 && ContextCompat.checkSelfPermission(getContext(),
|
if (Build.VERSION.SDK_INT >= 33 && ContextCompat.checkSelfPermission(getContext(),
|
||||||
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
SharedPreferences prefs = getContext().getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE);
|
|
||||||
if (!prefs.getBoolean(HomeFragment.PREF_DISABLE_NOTIFICATION_PERMISSION_NAG, false)) {
|
if (!prefs.getBoolean(HomeFragment.PREF_DISABLE_NOTIFICATION_PERMISSION_NAG, false)) {
|
||||||
addSection(new AllowNotificationsSection());
|
addSection(new AllowNotificationsSection());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Calendar.getInstance().get(Calendar.MONTH) == Calendar.DECEMBER
|
||||||
|
&& Calendar.getInstance().get(Calendar.YEAR) == 2023
|
||||||
|
&& Calendar.getInstance().get(Calendar.DAY_OF_MONTH) >= 10
|
||||||
|
&& prefs.getInt(PREF_HIDE_ECHO, 0) != 2023) {
|
||||||
|
addSection(new EchoSection());
|
||||||
|
}
|
||||||
|
|
||||||
List<String> hiddenSections = getHiddenSections(getContext());
|
List<String> hiddenSections = getHiddenSections(getContext());
|
||||||
String[] sectionTags = getResources().getStringArray(R.array.home_section_tags);
|
String[] sectionTags = getResources().getStringArray(R.array.home_section_tags);
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
package de.danoeh.antennapod.ui.home.sections;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import de.danoeh.antennapod.R;
|
||||||
|
import de.danoeh.antennapod.activity.MainActivity;
|
||||||
|
import de.danoeh.antennapod.core.storage.DBReader;
|
||||||
|
import de.danoeh.antennapod.core.storage.StatisticsItem;
|
||||||
|
import de.danoeh.antennapod.databinding.HomeSectionEchoBinding;
|
||||||
|
import de.danoeh.antennapod.ui.echo.EchoActivity;
|
||||||
|
import de.danoeh.antennapod.ui.home.HomeFragment;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
public class EchoSection extends Fragment {
|
||||||
|
private HomeSectionEchoBinding viewBinding;
|
||||||
|
private Disposable disposable;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
viewBinding = HomeSectionEchoBinding.inflate(inflater);
|
||||||
|
viewBinding.titleLabel.setText(getString(R.string.antennapod_echo_year, 2023));
|
||||||
|
viewBinding.echoButton.setOnClickListener(v -> startActivity(new Intent(getContext(), EchoActivity.class)));
|
||||||
|
viewBinding.closeButton.setOnClickListener(v -> {
|
||||||
|
getContext().getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
.edit().putInt(HomeFragment.PREF_HIDE_ECHO, 2023).apply();
|
||||||
|
((MainActivity) getActivity()).loadFragment(HomeFragment.TAG, null);
|
||||||
|
});
|
||||||
|
updateVisibility();
|
||||||
|
return viewBinding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long jan1() {
|
||||||
|
Calendar date = Calendar.getInstance();
|
||||||
|
date.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
date.set(Calendar.MINUTE, 0);
|
||||||
|
date.set(Calendar.SECOND, 0);
|
||||||
|
date.set(Calendar.MILLISECOND, 0);
|
||||||
|
date.set(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
date.set(Calendar.MONTH, 0);
|
||||||
|
date.set(Calendar.YEAR, 2023);
|
||||||
|
return date.getTimeInMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateVisibility() {
|
||||||
|
if (disposable != null) {
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
disposable = Observable.fromCallable(
|
||||||
|
() -> {
|
||||||
|
DBReader.StatisticsResult statisticsResult = DBReader.getStatistics(false, jan1(), Long.MAX_VALUE);
|
||||||
|
long totalTime = 0;
|
||||||
|
for (StatisticsItem feedTime : statisticsResult.feedTime) {
|
||||||
|
totalTime += feedTime.timePlayed;
|
||||||
|
}
|
||||||
|
return totalTime;
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(totalTime -> viewBinding.getRoot()
|
||||||
|
.setVisibility((totalTime >= 3600 * 10) ? View.VISIBLE : View.GONE),
|
||||||
|
Throwable::printStackTrace);
|
||||||
|
}
|
||||||
|
}
|
91
app/src/main/res/layout/home_section_echo.xml
Normal file
91
app/src/main/res/layout/home_section_echo.xml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginVertical="8dp"
|
||||||
|
android:accessibilityHeading="true"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/echo_home_header" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/closeButton"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/close_label"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:src="@drawable/ic_cancel" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="0dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/echoButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_blue_gradient"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:foreground="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:textColor="#fff"
|
||||||
|
android:text="@string/antennapod_echo_year"
|
||||||
|
android:textFontWeight="500"
|
||||||
|
style="@style/TextAppearance.Material3.TitleLarge" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#fff"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/echo_home_subtitle"
|
||||||
|
style="@style/TextAppearance.Material3.BodyMedium" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:textColor="#fff"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:src="@drawable/ic_arrow_right_white" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -806,6 +806,17 @@ public final class DBReader {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long getTimeBetweenReleaseAndPlayback(long timeFilterFrom, long timeFilterTo) {
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
try (Cursor cursor = adapter.getTimeBetweenReleaseAndPlayback(timeFilterFrom, timeFilterTo)) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
long result = Long.parseLong(cursor.getString(0));
|
||||||
|
adapter.close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns data necessary for displaying the navigation drawer. This includes
|
* Returns data necessary for displaying the navigation drawer. This includes
|
||||||
* the list of subscriptions, the number of items in the queue and the number of unread
|
* the list of subscriptions, the number of items in the queue and the number of unread
|
||||||
|
@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.util;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import de.danoeh.antennapod.core.R;
|
import de.danoeh.antennapod.core.R;
|
||||||
@ -82,17 +83,31 @@ public final class Converter {
|
|||||||
* Converts milliseconds to a localized string containing hours and minutes.
|
* Converts milliseconds to a localized string containing hours and minutes.
|
||||||
*/
|
*/
|
||||||
public static String getDurationStringLocalized(Context context, long duration) {
|
public static String getDurationStringLocalized(Context context, long duration) {
|
||||||
int h = (int) (duration / HOURS_MIL);
|
return getDurationStringLocalized(context.getResources(), duration);
|
||||||
int rest = (int) (duration - h * HOURS_MIL);
|
}
|
||||||
int m = rest / MINUTES_MIL;
|
|
||||||
|
|
||||||
|
public static String getDurationStringLocalized(Resources resources, long duration) {
|
||||||
String result = "";
|
String result = "";
|
||||||
if (h > 0) {
|
int h = (int) (duration / HOURS_MIL);
|
||||||
String hours = context.getResources().getQuantityString(R.plurals.time_hours_quantified, h, h);
|
int d = h / 24;
|
||||||
result += hours + " ";
|
if (d > 0) {
|
||||||
|
String days = resources.getQuantityString(R.plurals.time_days_quantified, d, d);
|
||||||
|
result += days.replace(" ", "\u00A0") + " ";
|
||||||
|
h -= d * 24;
|
||||||
|
}
|
||||||
|
int rest = (int) (duration - (d * 24 + h) * HOURS_MIL);
|
||||||
|
int m = rest / MINUTES_MIL;
|
||||||
|
if (h > 0) {
|
||||||
|
String hours = resources.getQuantityString(R.plurals.time_hours_quantified, h, h);
|
||||||
|
result += hours.replace(" ", "\u00A0");
|
||||||
|
if (d == 0) {
|
||||||
|
result += " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (d == 0) {
|
||||||
|
String minutes = resources.getQuantityString(R.plurals.time_minutes_quantified, m, m);
|
||||||
|
result += minutes.replace(" ", "\u00A0");
|
||||||
}
|
}
|
||||||
String minutes = context.getResources().getQuantityString(R.plurals.time_minutes_quantified, m, m);
|
|
||||||
result += minutes;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
core/src/main/res/drawable/bg_blue_gradient.xml
Normal file
10
core/src/main/res/drawable/bg_blue_gradient.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||||
|
<gradient
|
||||||
|
android:angle="90"
|
||||||
|
android:endColor="@color/gradient_025"
|
||||||
|
android:startColor="@color/gradient_075"
|
||||||
|
android:type="linear" />
|
||||||
|
<corners
|
||||||
|
android:radius="0dp"/>
|
||||||
|
</shape>
|
13
core/src/main/res/drawable/ic_arrow_right_white.xml
Normal file
13
core/src/main/res/drawable/ic_arrow_right_white.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z" />
|
||||||
|
|
||||||
|
</vector>
|
@ -23,4 +23,9 @@
|
|||||||
|
|
||||||
<color name="accent_light">#0078C2</color>
|
<color name="accent_light">#0078C2</color>
|
||||||
<color name="accent_dark">#3D8BFF</color>
|
<color name="accent_dark">#3D8BFF</color>
|
||||||
|
|
||||||
|
<color name="gradient_000">#364ff3</color>
|
||||||
|
<color name="gradient_025">#2E6FF6</color>
|
||||||
|
<color name="gradient_075">#1EB0FC</color>
|
||||||
|
<color name="gradient_100">#16d0ff</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -21,6 +21,7 @@ include ':storage:preferences'
|
|||||||
|
|
||||||
include ':ui:app-start-intent'
|
include ':ui:app-start-intent'
|
||||||
include ':ui:common'
|
include ':ui:common'
|
||||||
|
include ':ui:echo'
|
||||||
include ':ui:glide'
|
include ':ui:glide'
|
||||||
include ':ui:i18n'
|
include ':ui:i18n'
|
||||||
include ':ui:png-icons'
|
include ':ui:png-icons'
|
||||||
|
@ -1237,6 +1237,21 @@ public class PodDBAdapter {
|
|||||||
return db.rawQuery(query, null);
|
return db.rawQuery(query, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final Cursor getTimeBetweenReleaseAndPlayback(long timeFilterFrom, long timeFilterTo) {
|
||||||
|
final String from = " FROM " + TABLE_NAME_FEED_ITEMS
|
||||||
|
+ JOIN_FEED_ITEM_AND_MEDIA
|
||||||
|
+ " WHERE " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME + ">=" + timeFilterFrom
|
||||||
|
+ " AND " + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + ">=" + timeFilterFrom
|
||||||
|
+ " AND " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME + "<" + timeFilterTo;
|
||||||
|
final String query = "SELECT " + TABLE_NAME_FEED_MEDIA + "." + KEY_LAST_PLAYED_TIME
|
||||||
|
+ " - " + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + " AS diff"
|
||||||
|
+ from
|
||||||
|
+ " ORDER BY diff ASC"
|
||||||
|
+ " LIMIT 1"
|
||||||
|
+ " OFFSET (SELECT count(*)/2 " + from + ")";
|
||||||
|
return db.rawQuery(query, null);
|
||||||
|
}
|
||||||
|
|
||||||
public int getQueueSize() {
|
public int getQueueSize() {
|
||||||
final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_QUEUE);
|
final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_QUEUE);
|
||||||
Cursor c = db.rawQuery(query, null);
|
Cursor c = db.rawQuery(query, null);
|
||||||
|
3
ui/echo/README.md
Normal file
3
ui/echo/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# :ui:echo
|
||||||
|
|
||||||
|
This module provides the "Echo" screen, a yearly rewind.
|
27
ui/echo/build.gradle
Normal file
27
ui/echo/build.gradle
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.library")
|
||||||
|
}
|
||||||
|
apply from: "../../common.gradle"
|
||||||
|
apply from: "../../playFlavor.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "de.danoeh.antennapod.ui.echo"
|
||||||
|
|
||||||
|
lint {
|
||||||
|
disable "AppBundleLocaleChanges"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(":core")
|
||||||
|
implementation project(":model")
|
||||||
|
implementation project(":storage:preferences")
|
||||||
|
implementation project(':ui:glide')
|
||||||
|
|
||||||
|
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
|
||||||
|
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||||
|
implementation "com.google.android.material:material:$googleMaterialVersion"
|
||||||
|
implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
|
||||||
|
implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
|
||||||
|
implementation "com.github.bumptech.glide:glide:$glideVersion"
|
||||||
|
}
|
19
ui/echo/src/main/AndroidManifest.xml
Normal file
19
ui/echo/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:installLocation="auto">
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:supportsRtl="true">
|
||||||
|
<activity
|
||||||
|
android:label="@string/antennapod_echo"
|
||||||
|
android:name=".EchoActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Theme.AntennaPod.Dark.NoTitle">
|
||||||
|
<intent-filter>
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -0,0 +1,399 @@
|
|||||||
|
package de.danoeh.antennapod.ui.echo;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.app.ShareCompat;
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
import androidx.core.view.WindowCompat;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
|
||||||
|
import de.danoeh.antennapod.core.storage.DBReader;
|
||||||
|
import de.danoeh.antennapod.core.storage.StatisticsItem;
|
||||||
|
import de.danoeh.antennapod.core.util.Converter;
|
||||||
|
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||||
|
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||||
|
import de.danoeh.antennapod.ui.echo.databinding.EchoActivityBinding;
|
||||||
|
import de.danoeh.antennapod.ui.echo.screens.BubbleScreen;
|
||||||
|
import de.danoeh.antennapod.ui.echo.screens.FinalShareScreen;
|
||||||
|
import de.danoeh.antennapod.ui.echo.screens.RotatingSquaresScreen;
|
||||||
|
import de.danoeh.antennapod.ui.echo.screens.StripesScreen;
|
||||||
|
import de.danoeh.antennapod.ui.echo.screens.WaveformScreen;
|
||||||
|
import io.reactivex.Flowable;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
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 java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class EchoActivity extends AppCompatActivity {
|
||||||
|
private static final String TAG = "EchoActivity";
|
||||||
|
private static final int NUM_SCREENS = 7;
|
||||||
|
private static final int SHARE_SIZE = 1000;
|
||||||
|
|
||||||
|
private EchoActivityBinding viewBinding;
|
||||||
|
private int currentScreen = -1;
|
||||||
|
private boolean progressPaused = false;
|
||||||
|
private float progress = 0;
|
||||||
|
private Drawable currentDrawable;
|
||||||
|
private EchoProgress echoProgress;
|
||||||
|
private Disposable redrawTimer;
|
||||||
|
private long timeTouchDown;
|
||||||
|
private long timeLastFrame;
|
||||||
|
private Disposable disposable;
|
||||||
|
|
||||||
|
private long totalTime = 0;
|
||||||
|
private int totalActivePodcasts = 0;
|
||||||
|
private int playedPodcasts = 0;
|
||||||
|
private int playedActivePodcasts = 0;
|
||||||
|
private String randomUnplayedActivePodcast = "";
|
||||||
|
private int queueNumEpisodes = 0;
|
||||||
|
private long queueSecondsLeft = 0;
|
||||||
|
private long timeBetweenReleaseAndPlay = 0;
|
||||||
|
private long oldestDate = 0;
|
||||||
|
private final ArrayList<Pair<String, Drawable>> favoritePods = new ArrayList<>();
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
viewBinding = EchoActivityBinding.inflate(getLayoutInflater());
|
||||||
|
viewBinding.closeButton.setOnClickListener(v -> finish());
|
||||||
|
viewBinding.shareButton.setOnClickListener(v -> share());
|
||||||
|
viewBinding.echoImage.setOnTouchListener((v, event) -> {
|
||||||
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
progressPaused = true;
|
||||||
|
timeTouchDown = System.currentTimeMillis();
|
||||||
|
} else if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||||
|
progressPaused = false;
|
||||||
|
if (timeTouchDown + 500 > System.currentTimeMillis()) {
|
||||||
|
int newScreen;
|
||||||
|
if (event.getX() < 0.5f * viewBinding.echoImage.getMeasuredWidth()) {
|
||||||
|
newScreen = Math.max(currentScreen - 1, 0);
|
||||||
|
} else {
|
||||||
|
newScreen = Math.min(currentScreen + 1, NUM_SCREENS - 1);
|
||||||
|
if (currentScreen == NUM_SCREENS - 1) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progress = newScreen;
|
||||||
|
echoProgress.setProgress(progress);
|
||||||
|
loadScreen(newScreen, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
echoProgress = new EchoProgress(NUM_SCREENS);
|
||||||
|
viewBinding.echoProgressImage.setImageDrawable(echoProgress);
|
||||||
|
setContentView(viewBinding.getRoot());
|
||||||
|
loadScreen(0, false);
|
||||||
|
loadStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void share() {
|
||||||
|
try {
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(SHARE_SIZE, SHARE_SIZE, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
currentDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||||
|
currentDrawable.draw(canvas);
|
||||||
|
viewBinding.echoImage.setImageDrawable(null);
|
||||||
|
viewBinding.echoImage.setImageDrawable(currentDrawable);
|
||||||
|
File file = new File(UserPreferences.getDataFolder(null), "AntennaPodEcho.png");
|
||||||
|
FileOutputStream stream = new FileOutputStream(file);
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 90, stream);
|
||||||
|
stream.close();
|
||||||
|
|
||||||
|
Uri fileUri = FileProvider.getUriForFile(this, getString(R.string.provider_authority), file);
|
||||||
|
new ShareCompat.IntentBuilder(this)
|
||||||
|
.setType("image/png")
|
||||||
|
.addStream(fileUri)
|
||||||
|
.setText(getString(R.string.echo_share, 2023))
|
||||||
|
.setChooserTitle(R.string.share_file_label)
|
||||||
|
.startChooser();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
redrawTimer = Flowable.timer(20, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.repeat()
|
||||||
|
.subscribe(i -> {
|
||||||
|
if (progressPaused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
viewBinding.echoImage.postInvalidate();
|
||||||
|
if (progress >= NUM_SCREENS - 0.001f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long timePassed = System.currentTimeMillis() - timeLastFrame;
|
||||||
|
timeLastFrame = System.currentTimeMillis();
|
||||||
|
if (timePassed > 500) {
|
||||||
|
timePassed = 0;
|
||||||
|
}
|
||||||
|
progress = Math.min(NUM_SCREENS - 0.001f, progress + timePassed / 10000.0f);
|
||||||
|
echoProgress.setProgress(progress);
|
||||||
|
viewBinding.echoProgressImage.postInvalidate();
|
||||||
|
loadScreen((int) progress, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
redrawTimer.dispose();
|
||||||
|
if (disposable != null) {
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadScreen(int screen, boolean force) {
|
||||||
|
if (screen == currentScreen && !force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentScreen = screen;
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
viewBinding.echoLogo.setVisibility(currentScreen == 0 ? View.VISIBLE : View.GONE);
|
||||||
|
viewBinding.shareButton.setVisibility(currentScreen == 6 ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
switch (currentScreen) {
|
||||||
|
case 0:
|
||||||
|
viewBinding.aboveLabel.setText(R.string.echo_intro_your_year);
|
||||||
|
viewBinding.largeLabel.setText(String.format(getEchoLanguage(), "%d", 2023));
|
||||||
|
viewBinding.belowLabel.setText(R.string.echo_intro_in_podcasts);
|
||||||
|
viewBinding.smallLabel.setText(R.string.echo_intro_locally);
|
||||||
|
currentDrawable = new BubbleScreen(this);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
viewBinding.aboveLabel.setText(R.string.echo_hours_this_year);
|
||||||
|
viewBinding.largeLabel.setText(String.format(getEchoLanguage(), "%d", totalTime / 3600));
|
||||||
|
viewBinding.belowLabel.setText(getResources()
|
||||||
|
.getQuantityString(R.plurals.echo_hours_podcasts, playedPodcasts, playedPodcasts));
|
||||||
|
viewBinding.smallLabel.setText("");
|
||||||
|
currentDrawable = new WaveformScreen(this);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
viewBinding.largeLabel.setText(String.format(getEchoLanguage(), "%d", queueSecondsLeft / 3600));
|
||||||
|
viewBinding.belowLabel.setText(getResources().getQuantityString(
|
||||||
|
R.plurals.echo_queue_hours_waiting, queueNumEpisodes, queueNumEpisodes));
|
||||||
|
int daysUntil2024 = Math.max(356 - Calendar.getInstance().get(Calendar.DAY_OF_YEAR) + 1, 1);
|
||||||
|
long secondsPerDay = queueSecondsLeft / daysUntil2024;
|
||||||
|
String timePerDay = Converter.getDurationStringLocalized(
|
||||||
|
getLocalizedResources(this, getEchoLanguage()), secondsPerDay * 1000);
|
||||||
|
double hoursPerDay = (double) (secondsPerDay / 3600);
|
||||||
|
if (hoursPerDay < 1.5) {
|
||||||
|
viewBinding.aboveLabel.setText(R.string.echo_queue_title_clean);
|
||||||
|
viewBinding.smallLabel.setText(getString(R.string.echo_queue_hours_clean, timePerDay, 2024));
|
||||||
|
} else if (hoursPerDay <= 24) {
|
||||||
|
viewBinding.aboveLabel.setText(R.string.echo_queue_title_many);
|
||||||
|
viewBinding.smallLabel.setText(getString(R.string.echo_queue_hours_normal, timePerDay, 2024));
|
||||||
|
} else {
|
||||||
|
viewBinding.aboveLabel.setText(R.string.echo_queue_title_many);
|
||||||
|
viewBinding.smallLabel.setText(getString(R.string.echo_queue_hours_much, timePerDay, 2024));
|
||||||
|
}
|
||||||
|
currentDrawable = new StripesScreen(this);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
viewBinding.aboveLabel.setText(R.string.echo_listened_after_title);
|
||||||
|
if (timeBetweenReleaseAndPlay <= 1000L * 3600 * 24 * 2.5) {
|
||||||
|
viewBinding.largeLabel.setText(R.string.echo_listened_after_emoji_run);
|
||||||
|
viewBinding.belowLabel.setText(R.string.echo_listened_after_comment_addict);
|
||||||
|
} else {
|
||||||
|
viewBinding.largeLabel.setText(R.string.echo_listened_after_emoji_yoga);
|
||||||
|
viewBinding.belowLabel.setText(R.string.echo_listened_after_comment_easy);
|
||||||
|
}
|
||||||
|
viewBinding.smallLabel.setText(getString(R.string.echo_listened_after_time,
|
||||||
|
Converter.getDurationStringLocalized(
|
||||||
|
getLocalizedResources(this, getEchoLanguage()), timeBetweenReleaseAndPlay)));
|
||||||
|
currentDrawable = new RotatingSquaresScreen(this);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
viewBinding.aboveLabel.setText(R.string.echo_hoarder_title);
|
||||||
|
int percentagePlayed = (int) (100.0 * playedActivePodcasts / totalActivePodcasts);
|
||||||
|
if (percentagePlayed < 25) {
|
||||||
|
viewBinding.largeLabel.setText(R.string.echo_hoarder_emoji_cabinet);
|
||||||
|
viewBinding.belowLabel.setText(R.string.echo_hoarder_subtitle_hoarder);
|
||||||
|
viewBinding.smallLabel.setText(getString(R.string.echo_hoarder_comment_hoarder,
|
||||||
|
percentagePlayed, totalActivePodcasts));
|
||||||
|
} else if (percentagePlayed < 75) {
|
||||||
|
viewBinding.largeLabel.setText(R.string.echo_hoarder_emoji_check);
|
||||||
|
viewBinding.belowLabel.setText(R.string.echo_hoarder_subtitle_medium);
|
||||||
|
viewBinding.smallLabel.setText(getString(R.string.echo_hoarder_comment_medium,
|
||||||
|
percentagePlayed, totalActivePodcasts, randomUnplayedActivePodcast));
|
||||||
|
} else {
|
||||||
|
viewBinding.largeLabel.setText(R.string.echo_hoarder_emoji_clean);
|
||||||
|
viewBinding.belowLabel.setText(R.string.echo_hoarder_subtitle_clean);
|
||||||
|
viewBinding.smallLabel.setText(getString(R.string.echo_hoarder_comment_clean,
|
||||||
|
percentagePlayed, totalActivePodcasts));
|
||||||
|
}
|
||||||
|
currentDrawable = new StripesScreen(this);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
viewBinding.aboveLabel.setText("");
|
||||||
|
viewBinding.largeLabel.setText(R.string.echo_thanks_large);
|
||||||
|
if (oldestDate < jan1()) {
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("MMMM yyyy", getEchoLanguage());
|
||||||
|
String dateFrom = dateFormat.format(new Date(oldestDate));
|
||||||
|
viewBinding.belowLabel.setText(getString(R.string.echo_thanks_we_are_glad_old, dateFrom));
|
||||||
|
} else {
|
||||||
|
viewBinding.belowLabel.setText(R.string.echo_thanks_we_are_glad_new);
|
||||||
|
}
|
||||||
|
viewBinding.smallLabel.setText(R.string.echo_thanks_now_favorite);
|
||||||
|
currentDrawable = new RotatingSquaresScreen(this);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
viewBinding.aboveLabel.setText("");
|
||||||
|
viewBinding.largeLabel.setText("");
|
||||||
|
viewBinding.belowLabel.setText("");
|
||||||
|
viewBinding.smallLabel.setText("");
|
||||||
|
currentDrawable = new FinalShareScreen(this, favoritePods);
|
||||||
|
break;
|
||||||
|
default: // Keep
|
||||||
|
}
|
||||||
|
viewBinding.echoImage.setImageDrawable(currentDrawable);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Locale getEchoLanguage() {
|
||||||
|
boolean hasTranslation = !getString(R.string.echo_listened_after_title)
|
||||||
|
.equals(getLocalizedResources(this, Locale.US).getString(R.string.echo_listened_after_title));
|
||||||
|
if (hasTranslation) {
|
||||||
|
return Locale.getDefault();
|
||||||
|
} else {
|
||||||
|
return Locale.US;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Resources getLocalizedResources(Context context, Locale desiredLocale) {
|
||||||
|
Configuration conf = context.getResources().getConfiguration();
|
||||||
|
conf = new Configuration(conf);
|
||||||
|
conf.setLocale(desiredLocale);
|
||||||
|
Context localizedContext = context.createConfigurationContext(conf);
|
||||||
|
return localizedContext.getResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long jan1() {
|
||||||
|
Calendar date = Calendar.getInstance();
|
||||||
|
date.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
date.set(Calendar.MINUTE, 0);
|
||||||
|
date.set(Calendar.SECOND, 0);
|
||||||
|
date.set(Calendar.MILLISECOND, 0);
|
||||||
|
date.set(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
date.set(Calendar.MONTH, 0);
|
||||||
|
date.set(Calendar.YEAR, 2023);
|
||||||
|
return date.getTimeInMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadStatistics() {
|
||||||
|
if (disposable != null) {
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
long timeFilterFrom = jan1();
|
||||||
|
long timeFilterTo = Long.MAX_VALUE;
|
||||||
|
disposable = Observable.fromCallable(
|
||||||
|
() -> {
|
||||||
|
DBReader.StatisticsResult statisticsData = DBReader.getStatistics(
|
||||||
|
false, timeFilterFrom, timeFilterTo);
|
||||||
|
Collections.sort(statisticsData.feedTime, (item1, item2) ->
|
||||||
|
Long.compare(item2.timePlayed, item1.timePlayed));
|
||||||
|
|
||||||
|
favoritePods.clear();
|
||||||
|
for (int i = 0; i < 5 && i < statisticsData.feedTime.size(); i++) {
|
||||||
|
BitmapDrawable cover = new BitmapDrawable(getResources(), (Bitmap) null);
|
||||||
|
try {
|
||||||
|
final int size = SHARE_SIZE / 3;
|
||||||
|
final int radius = (i == 0) ? (size / 16) : (size / 8);
|
||||||
|
cover = new BitmapDrawable(getResources(), Glide.with(this)
|
||||||
|
.asBitmap()
|
||||||
|
.load(statisticsData.feedTime.get(i).feed.getImageUrl())
|
||||||
|
.apply(new RequestOptions()
|
||||||
|
.fitCenter()
|
||||||
|
.transform(new RoundedCorners(radius)))
|
||||||
|
.submit(size, size)
|
||||||
|
.get(1, TimeUnit.SECONDS));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
favoritePods.add(new Pair<>(statisticsData.feedTime.get(i).feed.getTitle(), cover));
|
||||||
|
}
|
||||||
|
|
||||||
|
totalActivePodcasts = 0;
|
||||||
|
playedActivePodcasts = 0;
|
||||||
|
playedPodcasts = 0;
|
||||||
|
totalTime = 0;
|
||||||
|
ArrayList<String> unplayedActive = new ArrayList<>();
|
||||||
|
for (StatisticsItem item : statisticsData.feedTime) {
|
||||||
|
totalTime += item.timePlayed;
|
||||||
|
if (item.timePlayed > 0) {
|
||||||
|
playedPodcasts++;
|
||||||
|
}
|
||||||
|
if (item.feed.getPreferences().getKeepUpdated()) {
|
||||||
|
totalActivePodcasts++;
|
||||||
|
if (item.timePlayed > 0) {
|
||||||
|
playedActivePodcasts++;
|
||||||
|
} else {
|
||||||
|
unplayedActive.add(item.feed.getTitle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!unplayedActive.isEmpty()) {
|
||||||
|
randomUnplayedActivePodcast = unplayedActive.get((int) (Math.random() * unplayedActive.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FeedItem> queue = DBReader.getQueue();
|
||||||
|
queueNumEpisodes = queue.size();
|
||||||
|
queueSecondsLeft = 0;
|
||||||
|
for (FeedItem item : queue) {
|
||||||
|
float playbackSpeed = 1;
|
||||||
|
if (UserPreferences.timeRespectsSpeed()) {
|
||||||
|
playbackSpeed = PlaybackSpeedUtils.getCurrentPlaybackSpeed(item.getMedia());
|
||||||
|
}
|
||||||
|
if (item.getMedia() != null) {
|
||||||
|
long itemTimeLeft = item.getMedia().getDuration() - item.getMedia().getPosition();
|
||||||
|
queueSecondsLeft += itemTimeLeft / playbackSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queueSecondsLeft /= 1000;
|
||||||
|
|
||||||
|
timeBetweenReleaseAndPlay = DBReader.getTimeBetweenReleaseAndPlayback(timeFilterFrom, timeFilterTo);
|
||||||
|
oldestDate = statisticsData.oldestDate;
|
||||||
|
return statisticsData;
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(result -> loadScreen(currentScreen, true),
|
||||||
|
error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package de.danoeh.antennapod.ui.echo;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class EchoProgress extends Drawable {
|
||||||
|
private final Paint paint;
|
||||||
|
private final int numScreens;
|
||||||
|
private float progress = 0;
|
||||||
|
|
||||||
|
public EchoProgress(int numScreens) {
|
||||||
|
this.numScreens = numScreens;
|
||||||
|
paint = new Paint();
|
||||||
|
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
paint.setStrokeJoin(Paint.Join.ROUND);
|
||||||
|
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
paint.setColor(0xffffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(float progress) {
|
||||||
|
this.progress = progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(@NonNull Canvas canvas) {
|
||||||
|
paint.setStrokeWidth(0.5f * getBounds().height());
|
||||||
|
|
||||||
|
float y = 0.5f * getBounds().height();
|
||||||
|
float sectionWidth = 1.0f * getBounds().width() / numScreens;
|
||||||
|
float sectionPadding = 0.03f * sectionWidth;
|
||||||
|
|
||||||
|
for (int i = 0; i < numScreens; i++) {
|
||||||
|
if (i + 1 < progress) {
|
||||||
|
paint.setAlpha(255);
|
||||||
|
} else {
|
||||||
|
paint.setAlpha(100);
|
||||||
|
}
|
||||||
|
canvas.drawLine(i * sectionWidth + sectionPadding, y, (i + 1) * sectionWidth - sectionPadding, y, paint);
|
||||||
|
if (Math.floor(1.0 * i) == Math.floor(progress)) {
|
||||||
|
paint.setAlpha(255);
|
||||||
|
canvas.drawLine(i * sectionWidth + sectionPadding, y, i * sectionWidth + sectionPadding
|
||||||
|
+ (progress - i) * (sectionWidth - 2 * sectionPadding), y, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return PixelFormat.TRANSLUCENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(ColorFilter cf) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package de.danoeh.antennapod.ui.echo.screens;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.LinearGradient;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
|
import android.graphics.Shader;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import de.danoeh.antennapod.ui.echo.R;
|
||||||
|
|
||||||
|
public abstract class BaseScreen extends Drawable {
|
||||||
|
private final Paint paintBackground;
|
||||||
|
protected final Paint paintParticles;
|
||||||
|
protected final ArrayList<Particle> particles = new ArrayList<>();
|
||||||
|
private final int colorBackgroundFrom;
|
||||||
|
private final int colorBackgroundTo;
|
||||||
|
private long lastFrame = 0;
|
||||||
|
|
||||||
|
public BaseScreen(Context context) {
|
||||||
|
colorBackgroundFrom = ContextCompat.getColor(context, R.color.gradient_000);
|
||||||
|
colorBackgroundTo = ContextCompat.getColor(context, R.color.gradient_100);
|
||||||
|
paintBackground = new Paint();
|
||||||
|
paintParticles = new Paint();
|
||||||
|
paintParticles.setColor(0xffffffff);
|
||||||
|
paintParticles.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
paintParticles.setStyle(Paint.Style.FILL);
|
||||||
|
paintParticles.setAlpha(25);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(@NonNull Canvas canvas) {
|
||||||
|
float width = getBounds().width();
|
||||||
|
float height = getBounds().height();
|
||||||
|
paintBackground.setShader(new LinearGradient(0, 0, 0, height,
|
||||||
|
colorBackgroundFrom, colorBackgroundTo, Shader.TileMode.CLAMP));
|
||||||
|
canvas.drawRect(0, 0, width, height, paintBackground);
|
||||||
|
|
||||||
|
long timeSinceLastFrame = System.currentTimeMillis() - lastFrame;
|
||||||
|
lastFrame = System.currentTimeMillis();
|
||||||
|
if (timeSinceLastFrame > 500) {
|
||||||
|
timeSinceLastFrame = 0;
|
||||||
|
}
|
||||||
|
final float innerBoxSize = (Math.abs(width - height) < 0.001f) // Square share version
|
||||||
|
? (0.9f * width) : (0.9f * Math.min(width, 0.7f * height));
|
||||||
|
final float innerBoxX = (width - innerBoxSize) / 2;
|
||||||
|
final float innerBoxY = (height - innerBoxSize) / 2;
|
||||||
|
|
||||||
|
for (Particle p : particles) {
|
||||||
|
drawParticle(canvas, p, width, height, innerBoxX, innerBoxY, innerBoxSize);
|
||||||
|
particleTick(p, timeSinceLastFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawInner(canvas, innerBoxX, innerBoxY, innerBoxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void drawInner(Canvas canvas, float innerBoxX, float innerBoxY, float innerBoxSize) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void particleTick(Particle p, long timeSinceLastFrame);
|
||||||
|
|
||||||
|
protected abstract void drawParticle(@NonNull Canvas canvas, Particle p, float width, float height,
|
||||||
|
float innerBoxX, float innerBoxY, float innerBoxSize);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return PixelFormat.TRANSLUCENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(ColorFilter cf) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Particle {
|
||||||
|
double positionX;
|
||||||
|
double positionY;
|
||||||
|
double positionZ;
|
||||||
|
double speed;
|
||||||
|
|
||||||
|
public Particle(double positionX, double positionY, double positionZ, double speed) {
|
||||||
|
this.positionX = positionX;
|
||||||
|
this.positionY = positionY;
|
||||||
|
this.positionZ = positionZ;
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package de.danoeh.antennapod.ui.echo.screens;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class BubbleScreen extends BaseScreen {
|
||||||
|
protected static final double PARTICLE_SPEED = 0.00002;
|
||||||
|
protected static final int NUM_PARTICLES = 15;
|
||||||
|
|
||||||
|
public BubbleScreen(Context context) {
|
||||||
|
super(context);
|
||||||
|
for (int i = 0; i < NUM_PARTICLES; i++) {
|
||||||
|
particles.add(new Particle(Math.random(), 2.0 * Math.random() - 0.5, // Could already be off-screen
|
||||||
|
0, PARTICLE_SPEED + 2 * PARTICLE_SPEED * Math.random()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawParticle(@NonNull Canvas canvas, Particle p, float width, float height,
|
||||||
|
float innerBoxX, float innerBoxY, float innerBoxSize) {
|
||||||
|
canvas.drawCircle((float) (width * p.positionX), (float) (p.positionY * height),
|
||||||
|
innerBoxSize / 5, paintParticles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void particleTick(Particle p, long timeSinceLastFrame) {
|
||||||
|
p.positionY -= p.speed * timeSinceLastFrame;
|
||||||
|
if (p.positionY < -0.5) {
|
||||||
|
p.positionX = Math.random();
|
||||||
|
p.positionY = 1.5f;
|
||||||
|
p.speed = PARTICLE_SPEED + 2 * PARTICLE_SPEED * Math.random();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package de.danoeh.antennapod.ui.echo.screens;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.util.Pair;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
import androidx.core.content.res.ResourcesCompat;
|
||||||
|
import de.danoeh.antennapod.ui.echo.R;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class FinalShareScreen extends BubbleScreen {
|
||||||
|
private static final float[][] COVER_POSITIONS = new float[][]{ new float[] {0.0f, 0.0f},
|
||||||
|
new float[] {0.4f, 0.0f}, new float[] {0.4f, 0.2f}, new float[] {0.6f, 0.2f}, new float[] {0.8f, 0.2f}};
|
||||||
|
private final Paint paintTextMain;
|
||||||
|
private final Paint paintCoverBorder;
|
||||||
|
private final String heading;
|
||||||
|
private final Drawable logo;
|
||||||
|
private final ArrayList<Pair<String, Drawable>> favoritePods;
|
||||||
|
private final Typeface typefaceNormal;
|
||||||
|
private final Typeface typefaceBold;
|
||||||
|
|
||||||
|
public FinalShareScreen(Context context, ArrayList<Pair<String, Drawable>> favoritePods) {
|
||||||
|
super(context);
|
||||||
|
this.heading = context.getString(R.string.echo_share_heading);
|
||||||
|
this.logo = AppCompatResources.getDrawable(context, R.drawable.echo);
|
||||||
|
this.favoritePods = favoritePods;
|
||||||
|
typefaceNormal = ResourcesCompat.getFont(context, R.font.sarabun_regular);
|
||||||
|
typefaceBold = ResourcesCompat.getFont(context, R.font.sarabun_semi_bold);
|
||||||
|
paintTextMain = new Paint();
|
||||||
|
paintTextMain.setColor(0xffffffff);
|
||||||
|
paintTextMain.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
paintTextMain.setStyle(Paint.Style.FILL);
|
||||||
|
paintCoverBorder = new Paint();
|
||||||
|
paintCoverBorder.setColor(0xffffffff);
|
||||||
|
paintCoverBorder.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
paintCoverBorder.setStyle(Paint.Style.FILL);
|
||||||
|
paintCoverBorder.setAlpha(70);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void drawInner(Canvas canvas, float innerBoxX, float innerBoxY, float innerBoxSize) {
|
||||||
|
paintTextMain.setTextAlign(Paint.Align.CENTER);
|
||||||
|
paintTextMain.setTypeface(typefaceBold);
|
||||||
|
float headingSize = innerBoxSize / 14;
|
||||||
|
paintTextMain.setTextSize(headingSize);
|
||||||
|
canvas.drawText(heading, innerBoxX + 0.5f * innerBoxSize, innerBoxY + headingSize, paintTextMain);
|
||||||
|
paintTextMain.setTextSize(0.12f * innerBoxSize);
|
||||||
|
canvas.drawText("2023", innerBoxX + 0.8f * innerBoxSize, innerBoxY + 0.25f * innerBoxSize, paintTextMain);
|
||||||
|
|
||||||
|
paintTextMain.setTextAlign(Paint.Align.LEFT);
|
||||||
|
float fontSizePods = innerBoxSize / 18; // First one only
|
||||||
|
float textY = innerBoxY + 0.62f * innerBoxSize;
|
||||||
|
for (int i = 0; i < favoritePods.size(); i++) {
|
||||||
|
float coverSize = (i == 0) ? (0.4f * innerBoxSize) : (0.2f * innerBoxSize);
|
||||||
|
float coverX = COVER_POSITIONS[i][0];
|
||||||
|
float coverY = COVER_POSITIONS[i][1];
|
||||||
|
RectF logo1Pos = new RectF(innerBoxX + coverX * innerBoxSize,
|
||||||
|
innerBoxY + (coverY + 0.12f) * innerBoxSize,
|
||||||
|
innerBoxX + coverX * innerBoxSize + coverSize,
|
||||||
|
innerBoxY + (coverY + 0.12f) * innerBoxSize + coverSize);
|
||||||
|
logo1Pos.inset((int) (0.01f * innerBoxSize), (int) (0.01f * innerBoxSize));
|
||||||
|
float radius = (i == 0) ? (coverSize / 16) : (coverSize / 8);
|
||||||
|
canvas.drawRoundRect(logo1Pos, radius, radius, paintCoverBorder);
|
||||||
|
logo1Pos.inset((int) (0.003f * innerBoxSize), (int) (0.003f * innerBoxSize));
|
||||||
|
Rect pos = new Rect();
|
||||||
|
logo1Pos.round(pos);
|
||||||
|
favoritePods.get(i).second.setBounds(pos);
|
||||||
|
favoritePods.get(i).second.draw(canvas);
|
||||||
|
|
||||||
|
paintTextMain.setTextSize(fontSizePods);
|
||||||
|
canvas.drawText((i + 1) + ".", innerBoxX, textY, paintTextMain);
|
||||||
|
canvas.drawText(favoritePods.get(i).first, innerBoxX + 0.055f * innerBoxSize, textY, paintTextMain);
|
||||||
|
fontSizePods = innerBoxSize / 24; // Starting with second text is smaller
|
||||||
|
textY += 1.3f * fontSizePods;
|
||||||
|
paintTextMain.setTypeface(typefaceNormal);
|
||||||
|
}
|
||||||
|
|
||||||
|
double ratio = (1.0 * logo.getIntrinsicHeight()) / logo.getIntrinsicWidth();
|
||||||
|
logo.setBounds((int) (innerBoxX + 0.1 * innerBoxSize),
|
||||||
|
(int) (innerBoxY + innerBoxSize - 0.8 * innerBoxSize * ratio),
|
||||||
|
(int) (innerBoxX + 0.9 * innerBoxSize),
|
||||||
|
(int) (innerBoxY + innerBoxSize));
|
||||||
|
logo.draw(canvas);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package de.danoeh.antennapod.ui.echo.screens;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class RotatingSquaresScreen extends BaseScreen {
|
||||||
|
public RotatingSquaresScreen(Context context) {
|
||||||
|
super(context);
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
particles.add(new Particle(
|
||||||
|
0.3 * (float) (i % 4) + 0.05 + 0.1 * Math.random() - 0.05,
|
||||||
|
0.2 * (float) (i / 4) + 0.20 + 0.1 * Math.random() - 0.05,
|
||||||
|
Math.random(), 0.00001 * (2 * Math.random() + 2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawParticle(@NonNull Canvas canvas, Particle p, float width, float height,
|
||||||
|
float innerBoxX, float innerBoxY, float innerBoxSize) {
|
||||||
|
float x = (float) (p.positionX * width);
|
||||||
|
float y = (float) (p.positionY * height);
|
||||||
|
float size = innerBoxSize / 6;
|
||||||
|
canvas.save();
|
||||||
|
canvas.rotate((float) (360 * p.positionZ), x, y);
|
||||||
|
canvas.drawRect(x - size, y - size, x + size, y + size, paintParticles);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void particleTick(Particle p, long timeSinceLastFrame) {
|
||||||
|
p.positionZ += p.speed * timeSinceLastFrame;
|
||||||
|
if (p.positionZ > 1) {
|
||||||
|
p.positionZ -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package de.danoeh.antennapod.ui.echo.screens;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class StripesScreen extends BaseScreen {
|
||||||
|
protected static final int NUM_PARTICLES = 15;
|
||||||
|
|
||||||
|
public StripesScreen(Context context) {
|
||||||
|
super(context);
|
||||||
|
for (int i = 0; i < NUM_PARTICLES; i++) {
|
||||||
|
particles.add(new Particle(2f * i / NUM_PARTICLES - 1f, 0, 0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(@NonNull Canvas canvas) {
|
||||||
|
paintParticles.setStrokeWidth(0.05f * getBounds().width());
|
||||||
|
super.draw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawParticle(@NonNull Canvas canvas, Particle p, float width, float height,
|
||||||
|
float innerBoxX, float innerBoxY, float innerBoxSize) {
|
||||||
|
float strokeWidth = 0.05f * width;
|
||||||
|
float x = (float) (width * p.positionX);
|
||||||
|
canvas.drawLine(x, -strokeWidth, x + width, height + strokeWidth, paintParticles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void particleTick(Particle p, long timeSinceLastFrame) {
|
||||||
|
p.positionX += 0.00005 * timeSinceLastFrame;
|
||||||
|
if (p.positionX > 1f) {
|
||||||
|
p.positionX -= 2f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package de.danoeh.antennapod.ui.echo.screens;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class WaveformScreen extends BaseScreen {
|
||||||
|
protected static final int NUM_PARTICLES = 40;
|
||||||
|
|
||||||
|
public WaveformScreen(Context context) {
|
||||||
|
super(context);
|
||||||
|
for (int i = 0; i < NUM_PARTICLES; i++) {
|
||||||
|
particles.add(new Particle(1.1f + 1.1f * i / NUM_PARTICLES - 0.05f, 0, 0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawParticle(@NonNull Canvas canvas, Particle p, float width, float height,
|
||||||
|
float innerBoxX, float innerBoxY, float innerBoxSize) {
|
||||||
|
float x = (float) (width * p.positionX);
|
||||||
|
canvas.drawRect(x, height, x + (1.1f * width) / NUM_PARTICLES,
|
||||||
|
(float) (0.95f * height - 0.3f * p.positionY * height), paintParticles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void particleTick(Particle p, long timeSinceLastFrame) {
|
||||||
|
p.positionX += 0.0001 * timeSinceLastFrame;
|
||||||
|
if (p.positionY <= 0.2 || p.positionY >= 1) {
|
||||||
|
p.speed = -p.speed;
|
||||||
|
p.positionY -= p.speed * timeSinceLastFrame;
|
||||||
|
}
|
||||||
|
p.positionY -= p.speed * timeSinceLastFrame;
|
||||||
|
if (p.positionX > 1.05f) {
|
||||||
|
p.positionX -= 1.1;
|
||||||
|
p.positionY = 0.2 + 0.8 * Math.random();
|
||||||
|
p.speed = 0.0008 * Math.random() - 0.0004;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
ui/echo/src/main/res/drawable-nodpi/echo.png
Normal file
BIN
ui/echo/src/main/res/drawable-nodpi/echo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
ui/echo/src/main/res/drawable-nodpi/logo_monochrome.png
Normal file
BIN
ui/echo/src/main/res/drawable-nodpi/logo_monochrome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
BIN
ui/echo/src/main/res/font/sarabun_regular.ttf
Normal file
BIN
ui/echo/src/main/res/font/sarabun_regular.ttf
Normal file
Binary file not shown.
BIN
ui/echo/src/main/res/font/sarabun_semi_bold.ttf
Normal file
BIN
ui/echo/src/main/res/font/sarabun_semi_bold.ttf
Normal file
Binary file not shown.
135
ui/echo/src/main/res/layout/echo_activity.xml
Normal file
135
ui/echo/src/main/res/layout/echo_activity.xml
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/echoImage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:importantForAccessibility="no" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/echoProgressImage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/closeButton"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:src="@drawable/ic_close_white"
|
||||||
|
android:contentDescription="@string/close_label"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_below="@id/echoProgressImage" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:src="@drawable/logo_monochrome"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_below="@id/echoProgressImage" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:padding="32dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aboveLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:fontFamily="@font/sarabun_regular"
|
||||||
|
app:fontFamily="@font/sarabun_regular"
|
||||||
|
tools:text="text above"
|
||||||
|
style="@style/TextAppearance.Material3.TitleLarge" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/largeLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:layout_marginVertical="8dp"
|
||||||
|
android:fontFamily="@font/sarabun_semi_bold"
|
||||||
|
app:fontFamily="@font/sarabun_semi_bold"
|
||||||
|
tools:text="large"
|
||||||
|
style="@style/TextAppearance.Material3.DisplayLarge"
|
||||||
|
tools:targetApi="p" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/belowLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:fontFamily="@font/sarabun_regular"
|
||||||
|
app:fontFamily="@font/sarabun_regular"
|
||||||
|
tools:text="text below"
|
||||||
|
style="@style/TextAppearance.Material3.TitleLarge" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/smallLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:fontFamily="@font/sarabun_regular"
|
||||||
|
app:fontFamily="@font/sarabun_regular"
|
||||||
|
tools:text="small" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/echoLogo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_margin="32dp"
|
||||||
|
android:src="@drawable/echo"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:layout_alignParentBottom="true" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/shareButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_margin="32dp"
|
||||||
|
android:text="@string/share_label"
|
||||||
|
android:drawableLeft="@drawable/ic_share"
|
||||||
|
android:textColor="#fff"
|
||||||
|
android:contentDescription="@string/share_label"
|
||||||
|
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||||
|
app:strokeColor="#fff"
|
||||||
|
tools:ignore="RtlHardcoded" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
46
ui/echo/src/main/res/values-de/echo-strings.xml
Normal file
46
ui/echo/src/main/res/values-de/echo-strings.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||||
|
<string name="echo_home_header">Jahresrückblick</string>
|
||||||
|
<string name="echo_home_subtitle">Deine Lieblings-Podcasts und Statistiken aus dem letzten Jahr. Exklusiv auf deinem Telefon.</string>
|
||||||
|
|
||||||
|
<string name="echo_intro_your_year">Dein Podcast-Jahr</string>
|
||||||
|
<string name="echo_intro_in_podcasts"> </string>
|
||||||
|
<string name="echo_intro_locally">privat auf deinem Telefon generiert</string>
|
||||||
|
|
||||||
|
<string name="echo_hours_this_year">Dieses Jahr hast du</string>
|
||||||
|
<plurals name="echo_hours_podcasts">
|
||||||
|
<item quantity="one">Stunden an Episoden von %1$d Podcast abgespielt</item>
|
||||||
|
<item quantity="other">Stunden an Episoden von %1$d Podcasts abgespielt</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="echo_queue_title_clean">Und du bist bereit, das neue Jahr frisch zu starten. In deiner Warteschlange liegen</string>
|
||||||
|
<string name="echo_queue_title_many">Und du hast dieses Jahr noch eine ganze Menge vor dir. In deiner Warteschlange liegen</string>
|
||||||
|
<plurals name="echo_queue_hours_waiting">
|
||||||
|
<item quantity="one">Stunden aus %1$d Podcast-Episode</item>
|
||||||
|
<item quantity="other">Stunden aus %1$d Podcast-Episoden</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="echo_queue_hours_clean">Das sind jeden Tag ungefähr %1$s bevor %2$d beginnt</string>
|
||||||
|
<string name="echo_queue_hours_normal">Das sind jeden Tag ungefähr %1$s bevor %2$d beginnt. Du kannst frisch ins Jahr starten, wenn du einige Episoden überspringst.</string>
|
||||||
|
<string name="echo_queue_hours_much">Das sind jeden Tag ungefähr %1$s bevor %2$d beginnt. Moment, was?</string>
|
||||||
|
|
||||||
|
<string name="echo_listened_after_title">Wir haben uns angeschaut, wann Episoden veröffentlicht werden und wann du sie abspielst. Unsere Folgerung?</string>
|
||||||
|
<string name="echo_listened_after_comment_easy">Du bist entspannt</string>
|
||||||
|
<string name="echo_listened_after_time">Typischerweise spielst du eine Episode %1$s nach der Veröffentlichung ab.</string>
|
||||||
|
<string name="echo_listened_after_comment_addict">Du bist ein Podcast-Junkie</string>
|
||||||
|
|
||||||
|
<string name="echo_hoarder_title">Wir haben uns auch gefragt: hörst du die Podcasts an, die du abonniert hast?</string>
|
||||||
|
<string name="echo_hoarder_subtitle_hoarder">Wenn wir uns die Zahlen so anschauen, glauben wir, du hamsterst Podcasts</string>
|
||||||
|
<string name="echo_hoarder_comment_hoarder">Zahlen lügen nicht, sagt man. Du hast dieses Jahr nur %1$d%% deiner %2$d aktiven Abonnements abgespielt, also liegen wir wahrscheinlich richtig.</string>
|
||||||
|
<string name="echo_hoarder_subtitle_medium">Du hamsterst keine Podcasts</string>
|
||||||
|
<string name="echo_hoarder_comment_medium">Du hast dieses Jahr %1$d%% deiner %2$d aktiven Abonnements abgespielt. Wie wäre es damit, mal wieder \"%3$s\" anzuhören?</string>
|
||||||
|
<string name="echo_hoarder_subtitle_clean">Aufgeräumt!</string>
|
||||||
|
<string name="echo_hoarder_comment_clean">Du hast dieses Jahr %1$d%% deiner %2$d aktiven Abonnements abgespielt. Wetten, du hältst auch deinen Schreibtisch sauber?</string>
|
||||||
|
|
||||||
|
<string name="echo_thanks_large">Danke</string>
|
||||||
|
<string name="echo_thanks_we_are_glad_old">dass du dieses Jahr wieder dabei warst!\n\nDu hast deine erste Episode im %1$s abgespielt. Es ist uns eine Ehre, seitdem für dich da zu sein.</string>
|
||||||
|
<string name="echo_thanks_we_are_glad_new">dass du dich dieses Jahr für uns entschieden hast!\n\nEgal ob du von einer anderen App gekommen bist oder mit AntennaPod in Podcasts eingestiegen bist: Wir sind froh, dass du da bist!</string>
|
||||||
|
<string name="echo_thanks_now_favorite">Schauen wir uns jetzt noch deine Lieblings-Podcasts an…</string>
|
||||||
|
|
||||||
|
<string name="echo_share_heading">Meine Lieblings-Podcasts</string>
|
||||||
|
<string name="echo_share">Mein Podcast-Jahr %d. #AntennaPodEcho</string>
|
||||||
|
</resources>
|
46
ui/echo/src/main/res/values-es/echo-strings.xml
Normal file
46
ui/echo/src/main/res/values-es/echo-strings.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation,PluralsCandidate">
|
||||||
|
<string name="echo_home_header">Revisa el año</string>
|
||||||
|
<string name="echo_home_subtitle">Tus mejores pódcasts y estadísticas del año pasado. Exclusivamente en tu teléfono.</string>
|
||||||
|
|
||||||
|
<string name="echo_intro_your_year">Tu año</string>
|
||||||
|
<string name="echo_intro_in_podcasts">en pódcasts</string>
|
||||||
|
<string name="echo_intro_locally">generado de forma privada en tu teléfono</string>
|
||||||
|
|
||||||
|
<string name="echo_hours_this_year">Este año has reproducido</string>
|
||||||
|
<plurals name="echo_hours_podcasts">
|
||||||
|
<item quantity="one">horas de episodios\nde %1$d pódcast</item>
|
||||||
|
<item quantity="other">horas de episodios\nde %1$d pódcasts diferentes</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="echo_queue_title_clean">Y estás listo para empezar el año de nuevo. Tienes</string>
|
||||||
|
<string name="echo_queue_title_many">Y aún te queda bastante este año. Tienes</string>
|
||||||
|
<plurals name="echo_queue_hours_waiting">
|
||||||
|
<item quantity="one">hora esperando en tu cola\nde %1$d episodio</item>
|
||||||
|
<item quantity="other">horas esperando en tu cola\nde %1$d episodios</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="echo_queue_hours_clean">Eso es alrededor de %1$s cada día hasta que empiece %2$d</string>
|
||||||
|
<string name="echo_queue_hours_normal">Eso son %1$s cada día hasta que empiece %2$d. Puedes empezar el año de cero si te saltas algunos episodios.</string>
|
||||||
|
<string name="echo_queue_hours_much">Eso son %1$s cada día hasta que empiece %2$d. Espera, ¿qué?</string>
|
||||||
|
|
||||||
|
<string name="echo_listened_after_title">Hemos analizado cuándo se publican los episodios y cuándo los completaste. ¿Nuestra conclusión?</string>
|
||||||
|
<string name="echo_listened_after_comment_easy">Eres relajado</string>
|
||||||
|
<string name="echo_listened_after_time">Normalmente, completaste un episodio %1$s después de su publicación.</string>
|
||||||
|
<string name="echo_listened_after_comment_addict">Eres un adicto a los pódcasts</string>
|
||||||
|
|
||||||
|
<string name="echo_hoarder_title">También nos hemos preguntado: ¿Escuchas los pódcasts a los que estás suscrito?</string>
|
||||||
|
<string name="echo_hoarder_subtitle_hoarder">Viendo los números, creemos que eres un acumulador</string>
|
||||||
|
<string name="echo_hoarder_comment_hoarder">Dicen que los números no mienten. Y con solo %1$d%% de tus %2$d suscripciones activas reproducidas este año, probablemente tengamos razón.</string>
|
||||||
|
<string name="echo_hoarder_subtitle_medium">Mira. Aquí no hay acumulación.</string>
|
||||||
|
<string name="echo_hoarder_comment_medium">Has reproducido episodios de %1$d%% de tus %2$d suscripciones activas este año. ¿Qué tal si vuelves a escuchar \"%3$s\"?</string>
|
||||||
|
<string name="echo_hoarder_subtitle_clean">¡Limpio!</string>
|
||||||
|
<string name="echo_hoarder_comment_clean">Has reproducido episodios de %1$d%% de tus %2$d suscripciones activas este año. ¡Apostamos que también mantienes limpio tu escritorio!</string>
|
||||||
|
|
||||||
|
<string name="echo_thanks_large">¡Gracias</string>
|
||||||
|
<string name="echo_thanks_we_are_glad_old">por estar con nosotros este año!\n\nReproduciste tu primer episodio con nosotros en %1$s. Ha sido un honor servirte desde entonces.</string>
|
||||||
|
<string name="echo_thanks_we_are_glad_new">por unirte a nosotros este año!\n\nWTanto si vienes de otra aplicación como si has empezado tu aventura con los pódcasts con nosotros, ¡Estamos encantados de tenerte!</string>
|
||||||
|
<string name="echo_thanks_now_favorite">Ahora, echemos un vistazo a tus pódcasts favoritos…</string>
|
||||||
|
|
||||||
|
<string name="echo_share_heading">Mis pódcasts favoritos</string>
|
||||||
|
<string name="echo_share">Mi año %d en pódcasts. #AntennaPodEcho</string>
|
||||||
|
</resources>
|
48
ui/echo/src/main/res/values-fr/echo-strings.xml
Normal file
48
ui/echo/src/main/res/values-fr/echo-strings.xml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||||
|
<string name="echo_home_header">Bilan de l\'année</string>
|
||||||
|
<string name="echo_home_subtitle">Vos podcasts et statistiques de cette année. Exclusivement sur votre téléphone.</string>
|
||||||
|
|
||||||
|
<string name="echo_intro_your_year">Votre année</string>
|
||||||
|
<string name="echo_intro_in_podcasts">de podcasts</string>
|
||||||
|
<string name="echo_intro_locally">généré par votre téléphone pour respecter votre vie privée</string>
|
||||||
|
|
||||||
|
<string name="echo_hours_this_year">Cette année vous avez écouté</string>
|
||||||
|
<plurals name="echo_hours_podcasts">
|
||||||
|
<item quantity="one">heures d\'épisodes\nd\'%1$d seul podcast</item>
|
||||||
|
<item quantity="many">heures d\'épisodes\nde %1$d podcasts différents</item>
|
||||||
|
<item quantity="other">heures d\'épisodes\nde %1$d podcasts différents</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="echo_queue_title_clean">Et vous êtes prêt à finir à l\'année avec seulement</string>
|
||||||
|
<string name="echo_queue_title_many">Et vous avez de la réserve pour finir l\'année avec</string>
|
||||||
|
<plurals name="echo_queue_hours_waiting">
|
||||||
|
<item quantity="one">heures pour finir votre liste de lecture\nd\'%1$d seul épisode</item>
|
||||||
|
<item quantity="many">heures finir votre liste de lecture\nde %1$d épisodes</item>
|
||||||
|
<item quantity="other">heures finir votre liste de lecture\nde %1$d épisodes</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="echo_queue_hours_clean">C\'est environ %1$s tous les jours avant que %2$d ne commence.</string>
|
||||||
|
<string name="echo_queue_hours_normal">C\'est environ %1$s tous les jours avant que %2$d ne commence. Si vous voulez y arriver des épisodes vont devoir être sautés !</string>
|
||||||
|
<string name="echo_queue_hours_much">C\'est environ %1$s tous les jours avant que %2$d ne commence. Hein !? Ça semble tendu !</string>
|
||||||
|
|
||||||
|
<string name="echo_listened_after_title">On a lancé quelques analyses entre le moment où un épisode est publié et son écoute. Notre conclusion ?</string>
|
||||||
|
<string name="echo_listened_after_comment_easy">Vous êtes zen</string>
|
||||||
|
<string name="echo_listened_after_time">En général, vous avez écouté un episode %1$s après sa publication.</string>
|
||||||
|
<string name="echo_listened_after_comment_addict">Vous êtes accro aux podcasts</string>
|
||||||
|
|
||||||
|
<string name="echo_hoarder_title">On s\'est aussi demandé si vous écoutiez les podcasts auxquels vous êtes abonné ?</string>
|
||||||
|
<string name="echo_hoarder_subtitle_hoarder">Au vu des chiffres on a détecté un petit syndrome de collectionnite !</string>
|
||||||
|
<string name="echo_hoarder_comment_hoarder">Et il parait que les chiffres ne mentent pas. Avec seulement %1$d%% de vos %2$d abonnements actifs ayant été lu cette année, nous avons probablement raison.</string>
|
||||||
|
<string name="echo_hoarder_subtitle_medium">Rien à signaler ! Pas de collectionnite aigüe détectée !</string>
|
||||||
|
<string name="echo_hoarder_comment_medium">Vous avez lu %1$d%% de vos %2$d abonnements actifs cette année. Pourquoi ne pas allez rejeter un coup d\'oeil à \"%3$s\" ?</string>
|
||||||
|
<string name="echo_hoarder_subtitle_clean">Nickel !</string>
|
||||||
|
<string name="echo_hoarder_comment_clean">Vous avez lu %1$d%% de vos %2$d abonnements actifs cette année. On parie que vous ête du genre à avoir un bureau propre !</string>
|
||||||
|
|
||||||
|
<string name="echo_thanks_large">Merci</string>
|
||||||
|
<string name="echo_thanks_we_are_glad_old">d\'avoir partagé cette année avec nous !\n\nVotre premier épisode avec nous a été lu en %1$s et on est fier de continuer à vous être utile.</string>
|
||||||
|
<string name="echo_thanks_we_are_glad_new">de nous avoir rejoint cette année !\n\nPeu importe si vous avez changé d\'application ou commencé à écouter les podcasts avec nous : on est content de vous avoir !</string>
|
||||||
|
<string name="echo_thanks_now_favorite">Maintenant, jetons un coup d\'oeil à vos podcasts préférés…</string>
|
||||||
|
|
||||||
|
<string name="echo_share_heading">Mes podcasts préférés</string>
|
||||||
|
<string name="echo_share">Mon année %d en podcasts. #AntennaPodEcho</string>
|
||||||
|
</resources>
|
46
ui/echo/src/main/res/values-it/echo-strings.xml
Normal file
46
ui/echo/src/main/res/values-it/echo-strings.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||||
|
<string name="echo_home_header">Passa in rassegna l\'anno</string>
|
||||||
|
<string name="echo_home_subtitle">I tuoi podcast più ascoltati e statistiche sull\'anno appena trascorso. In esclusiva sul tuo telefono.</string>
|
||||||
|
|
||||||
|
<string name="echo_intro_your_year">Il tuo anno</string>
|
||||||
|
<string name="echo_intro_in_podcasts">in podcast</string>
|
||||||
|
<string name="echo_intro_locally">generato in privato sul tuo telefono</string>
|
||||||
|
|
||||||
|
<string name="echo_hours_this_year">Quest\'anno hai riprodotto</string>
|
||||||
|
<plurals name="echo_hours_podcasts">
|
||||||
|
<item quantity="one">ore di episodi\nda %1$d podcast</item>
|
||||||
|
<item quantity="other">ore di episodi\nda %1$d podcast diversi</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="echo_queue_title_clean">E sei pronto a cominciare da zero il nuovo anno. Hai</string>
|
||||||
|
<string name="echo_queue_title_many">E ti resta ancora un bel po\' da fare quest\'anno. Hai</string>
|
||||||
|
<plurals name="echo_queue_hours_waiting">
|
||||||
|
<item quantity="one">ore provenienti da %1$d episodio\nin attesa nella tua coda</item>
|
||||||
|
<item quantity="other">ore provenienti da %1$d episodi\nin attesa nella tua coda</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="echo_queue_hours_clean">Cioè circa %1$s al giorno da qui al %2$d.</string>
|
||||||
|
<string name="echo_queue_hours_normal">Cioè circa %1$s al giorno da qui al %2$d. Puoi cominciare da zero il nuovo anno se salti qualche episodio.</string>
|
||||||
|
<string name="echo_queue_hours_much">Cioè circa %1$s al giorno da qui al %2$d. Aspetta, come hai detto?</string>
|
||||||
|
|
||||||
|
<string name="echo_listened_after_title">Abbiamo analizzato quando gli episodi sono pubblicati e quando finisci di ascoltarli. La nostra conclusione?</string>
|
||||||
|
<string name="echo_listened_after_comment_easy">Te la prendi comoda</string>
|
||||||
|
<string name="echo_listened_after_time">In genere, finisci di ascoltare un episodio %1$s dopo la sua pubblicazione.</string>
|
||||||
|
<string name="echo_listened_after_comment_addict">Sei podcast-dipendente</string>
|
||||||
|
|
||||||
|
<string name="echo_hoarder_title">Ci siamo chiesti anche: ascolti davvero i podcast a cui sei iscritto?</string>
|
||||||
|
<string name="echo_hoarder_subtitle_hoarder">Dati alla mano, pensiamo che tu sia un accumulatore</string>
|
||||||
|
<string name="echo_hoarder_comment_hoarder">Si dice che i numeri non mentano. E visto che quest\'anno hai riprodotto solo il %1$d%% delle tue %2$d iscrizioni attive, probabilmente abbiamo ragione.</string>
|
||||||
|
<string name="echo_hoarder_subtitle_medium">Verificato. Non c\'è traccia di accumulo seriale.</string>
|
||||||
|
<string name="echo_hoarder_comment_medium">Quest\'anno hai riprodotto episodi dal %1$d%% delle tue %2$d iscrizioni attive. Che ne dici di ascoltare di nuovo \"%3$s\"?</string>
|
||||||
|
<string name="echo_hoarder_subtitle_clean">In ordine!</string>
|
||||||
|
<string name="echo_hoarder_comment_clean">Quest\'anno hai riprodotto episodi dal %1$d%% delle tue %2$d iscrizioni attive. Scommettiamo che tieni in ordine anche la tua scrivania!</string>
|
||||||
|
|
||||||
|
<string name="echo_thanks_large">Grazie</string>
|
||||||
|
<string name="echo_thanks_we_are_glad_old">per essere rimasto con noi quest\'anno!\n\nHai riprodotto il tuo primo episodio con noi in %1$s. Da allora, siamo onorati di essere al tuo servizio.</string>
|
||||||
|
<string name="echo_thanks_we_are_glad_new">per esserti unito a noi quest\'anno!\n\nChe tu sia arrivato qui da un\'altra app o che tu abbia iniziato la tua avventura coi podcast con noi, siamo felici di averti qui!</string>
|
||||||
|
<string name="echo_thanks_now_favorite">Ora diamo un\'occhiata ai tuoi podcast preferiti…</string>
|
||||||
|
|
||||||
|
<string name="echo_share_heading">I miei podcast preferiti</string>
|
||||||
|
<string name="echo_share">Il mio %d in podcast. #AntennaPodEcho</string>
|
||||||
|
</resources>
|
51
ui/echo/src/main/res/values/echo-strings.xml
Normal file
51
ui/echo/src/main/res/values/echo-strings.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation,PluralsCandidate">
|
||||||
|
<string name="echo_home_header">Review the year</string>
|
||||||
|
<string name="echo_home_subtitle">Your top podcasts and stats from the past year. Exclusively on your phone.</string>
|
||||||
|
|
||||||
|
<string name="echo_intro_your_year">Your year</string>
|
||||||
|
<string name="echo_intro_in_podcasts">in podcasts</string>
|
||||||
|
<string name="echo_intro_locally">generated privately on your phone</string>
|
||||||
|
|
||||||
|
<string name="echo_hours_this_year">This year you played</string>
|
||||||
|
<plurals name="echo_hours_podcasts">
|
||||||
|
<item quantity="one">hours of episodes\nfrom %1$d podcast</item>
|
||||||
|
<item quantity="other">hours of episodes\nfrom %1$d different podcasts</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="echo_queue_title_clean">And you\'re ready to make a clean start of the year. You have</string>
|
||||||
|
<string name="echo_queue_title_many">And you still have quite a bit to go this year. You have</string>
|
||||||
|
<plurals name="echo_queue_hours_waiting">
|
||||||
|
<item quantity="one">hours waiting in your queue\nfrom %1$d episode</item>
|
||||||
|
<item quantity="other">hours waiting in your queue\nfrom %1$d episodes</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="echo_queue_hours_clean">That\'s about %1$s each day until %2$d starts.</string>
|
||||||
|
<string name="echo_queue_hours_normal">That\'s about %1$s each day until %2$d starts. You can start the year clean if you skip a few episodes.</string>
|
||||||
|
<string name="echo_queue_hours_much">That\'s about %1$s each day until %2$d starts. Wait, what?</string>
|
||||||
|
|
||||||
|
<string name="echo_listened_after_title">We\'ve run some analysis on when episodes are released, and when you completed them. Our conclusion?</string>
|
||||||
|
<string name="echo_listened_after_emoji_yoga" translatable="false">\uD83E\uDDD8</string>
|
||||||
|
<string name="echo_listened_after_comment_easy">You\'re easy going</string>
|
||||||
|
<string name="echo_listened_after_time">Typically, you completed an episode %1$s after it was released.</string>
|
||||||
|
<string name="echo_listened_after_emoji_run" translatable="false">\uD83C\uDFC3</string>
|
||||||
|
<string name="echo_listened_after_comment_addict">You\'re a podcast addict</string>
|
||||||
|
|
||||||
|
<string name="echo_hoarder_title">We\'ve also been wondering: do you listen to the podcasts that you\'re subscribed to?</string>
|
||||||
|
<string name="echo_hoarder_subtitle_hoarder">Looking at the numbers, we think you\'re a hoarder</string>
|
||||||
|
<string name="echo_hoarder_emoji_cabinet" translatable="false">\uD83D\uDDC4\uFE0F</string>
|
||||||
|
<string name="echo_hoarder_comment_hoarder">Numbers don\'t lie, they say. And with only %1$d%% of your %2$d active subscriptions having been played this year, we\'re probably right.</string>
|
||||||
|
<string name="echo_hoarder_subtitle_medium">Check. No hoarding here.</string>
|
||||||
|
<string name="echo_hoarder_emoji_check" translatable="false">\u2705</string>
|
||||||
|
<string name="echo_hoarder_comment_medium">You\'ve played episodes from %1$d%% of your %2$d active subscriptions this year. How about checking out \"%3$s\" again?</string>
|
||||||
|
<string name="echo_hoarder_subtitle_clean">Clean!</string>
|
||||||
|
<string name="echo_hoarder_emoji_clean" translatable="false">\u2728</string>
|
||||||
|
<string name="echo_hoarder_comment_clean">You\'ve played episodes from %1$d%% of your %2$d active subscriptions this year. We bet you keep your desk clean, too!</string>
|
||||||
|
|
||||||
|
<string name="echo_thanks_large">Thanks</string>
|
||||||
|
<string name="echo_thanks_we_are_glad_old">for sticking with us this year!\n\nYou played your first episode with us in %1$s. It\'s been our honor to serve you since.</string>
|
||||||
|
<string name="echo_thanks_we_are_glad_new">for joining us this year!\n\nWhether you\'ve moved over from another app, or started your podcast adventure with us: we\'re glad to have you!</string>
|
||||||
|
<string name="echo_thanks_now_favorite">Now, let\'s take a look at your favorite podcasts…</string>
|
||||||
|
|
||||||
|
<string name="echo_share_heading">My favorite podcasts</string>
|
||||||
|
<string name="echo_share">My year %d in podcasts. #AntennaPodEcho</string>
|
||||||
|
</resources>
|
@ -27,6 +27,8 @@
|
|||||||
<string name="years_statistics_label">Years</string>
|
<string name="years_statistics_label">Years</string>
|
||||||
<string name="notification_pref_fragment">Notifications</string>
|
<string name="notification_pref_fragment">Notifications</string>
|
||||||
<string name="recently_played_episodes">Recently played episodes</string>
|
<string name="recently_played_episodes">Recently played episodes</string>
|
||||||
|
<string name="antennapod_echo" translatable="false">AntennaPod Echo</string>
|
||||||
|
<string name="antennapod_echo_year" translatable="false">AntennaPod Echo %d</string>
|
||||||
|
|
||||||
<!-- Google Assistant -->
|
<!-- Google Assistant -->
|
||||||
<string name="app_action_not_found">\"%1$s\" not found</string>
|
<string name="app_action_not_found">\"%1$s\" not found</string>
|
||||||
@ -620,6 +622,10 @@
|
|||||||
<item quantity="one">1 hour</item>
|
<item quantity="one">1 hour</item>
|
||||||
<item quantity="other">%d hours</item>
|
<item quantity="other">%d hours</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="time_days_quantified">
|
||||||
|
<item quantity="one">1 day</item>
|
||||||
|
<item quantity="other">%d days</item>
|
||||||
|
</plurals>
|
||||||
<string name="auto_enable_label">Automatically activate the sleep timer when pressing play</string>
|
<string name="auto_enable_label">Automatically activate the sleep timer when pressing play</string>
|
||||||
<string name="auto_enable_label_with_times">Automatically activate the sleep timer when pressing play between %s and %s</string>
|
<string name="auto_enable_label_with_times">Automatically activate the sleep timer when pressing play between %s and %s</string>
|
||||||
<string name="auto_enable_change_times">Change time range</string>
|
<string name="auto_enable_change_times">Change time range</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user