AntennaPod Echo (#6780)
This commit is contained in:
parent
637230e382
commit
ee554d0306
@ -89,6 +89,7 @@ dependencies {
|
||||
implementation project(':storage:preferences')
|
||||
implementation project(':ui:app-start-intent')
|
||||
implementation project(':ui:common')
|
||||
implementation project(':ui:echo')
|
||||
implementation project(':ui:glide')
|
||||
implementation project(':ui:i18n')
|
||||
implementation project(':ui:statistics')
|
||||
|
@ -21,12 +21,14 @@ import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentContainerView;
|
||||
|
||||
import de.danoeh.antennapod.ui.home.sections.EchoSection;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
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_HIDDEN_SECTIONS = "PrefHomeSectionsString";
|
||||
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 boolean displayUpArrow;
|
||||
@ -94,13 +97,19 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
|
||||
private void populateSectionList() {
|
||||
viewBinding.homeContainer.removeAllViews();
|
||||
|
||||
SharedPreferences prefs = getContext().getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE);
|
||||
if (Build.VERSION.SDK_INT >= 33 && ContextCompat.checkSelfPermission(getContext(),
|
||||
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)) {
|
||||
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());
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
* 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.res.Resources;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.danoeh.antennapod.core.R;
|
||||
@ -82,17 +83,31 @@ public final class Converter {
|
||||
* Converts milliseconds to a localized string containing hours and minutes.
|
||||
*/
|
||||
public static String getDurationStringLocalized(Context context, long duration) {
|
||||
int h = (int) (duration / HOURS_MIL);
|
||||
int rest = (int) (duration - h * HOURS_MIL);
|
||||
int m = rest / MINUTES_MIL;
|
||||
return getDurationStringLocalized(context.getResources(), duration);
|
||||
}
|
||||
|
||||
public static String getDurationStringLocalized(Resources resources, long duration) {
|
||||
String result = "";
|
||||
if (h > 0) {
|
||||
String hours = context.getResources().getQuantityString(R.plurals.time_hours_quantified, h, h);
|
||||
result += hours + " ";
|
||||
int h = (int) (duration / HOURS_MIL);
|
||||
int d = h / 24;
|
||||
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;
|
||||
}
|
||||
|
||||
|
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_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>
|
||||
|
@ -21,6 +21,7 @@ include ':storage:preferences'
|
||||
|
||||
include ':ui:app-start-intent'
|
||||
include ':ui:common'
|
||||
include ':ui:echo'
|
||||
include ':ui:glide'
|
||||
include ':ui:i18n'
|
||||
include ':ui:png-icons'
|
||||
|
@ -1237,6 +1237,21 @@ public class PodDBAdapter {
|
||||
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() {
|
||||
final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_QUEUE);
|
||||
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="notification_pref_fragment">Notifications</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 -->
|
||||
<string name="app_action_not_found">\"%1$s\" not found</string>
|
||||
@ -620,6 +622,10 @@
|
||||
<item quantity="one">1 hour</item>
|
||||
<item quantity="other">%d hours</item>
|
||||
</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_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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user