Restructure Echo to be more flexible (#7035)

Each screen is its own file, which makes it easier to add interactive elements.
This commit is contained in:
ByteHamster 2024-03-29 08:55:13 +01:00 committed by GitHub
parent 1dbda2fb8a
commit 13a985ca1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 752 additions and 447 deletions

View File

@ -19,6 +19,7 @@ import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentContainerView;
import de.danoeh.antennapod.ui.echo.EchoConfig;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@ -37,7 +38,6 @@ import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.fragment.SearchFragment;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.ui.echo.EchoActivity;
import de.danoeh.antennapod.ui.home.sections.AllowNotificationsSection;
import de.danoeh.antennapod.ui.home.sections.DownloadsSection;
import de.danoeh.antennapod.ui.home.sections.EchoSection;
@ -98,10 +98,10 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
addSection(new AllowNotificationsSection());
}
}
if (Calendar.getInstance().get(Calendar.YEAR) == EchoActivity.RELEASE_YEAR
if (Calendar.getInstance().get(Calendar.YEAR) == EchoConfig.RELEASE_YEAR
&& Calendar.getInstance().get(Calendar.MONTH) == Calendar.DECEMBER
&& Calendar.getInstance().get(Calendar.DAY_OF_MONTH) >= 10
&& prefs.getInt(PREF_HIDE_ECHO, 0) != EchoActivity.RELEASE_YEAR) {
&& prefs.getInt(PREF_HIDE_ECHO, 0) != EchoConfig.RELEASE_YEAR) {
addSection(new EchoSection());
}

View File

@ -11,18 +11,17 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.databinding.HomeSectionEchoBinding;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.StatisticsItem;
import de.danoeh.antennapod.databinding.HomeSectionEchoBinding;
import de.danoeh.antennapod.ui.echo.EchoActivity;
import de.danoeh.antennapod.ui.echo.EchoConfig;
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;
@ -32,32 +31,21 @@ public class EchoSection extends Fragment {
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, EchoActivity.RELEASE_YEAR));
viewBinding.titleLabel.setText(getString(R.string.antennapod_echo_year, EchoConfig.RELEASE_YEAR));
viewBinding.echoButton.setOnClickListener(v -> startActivity(new Intent(getContext(), EchoActivity.class)));
viewBinding.closeButton.setOnClickListener(v -> hideThisYear());
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, EchoActivity.RELEASE_YEAR);
return date.getTimeInMillis();
}
private void updateVisibility() {
if (disposable != null) {
disposable.dispose();
}
disposable = Observable.fromCallable(
() -> {
DBReader.StatisticsResult statisticsResult = DBReader.getStatistics(false, jan1(), Long.MAX_VALUE);
DBReader.StatisticsResult statisticsResult = DBReader.getStatistics(
false, EchoConfig.jan1(), Long.MAX_VALUE);
long totalTime = 0;
for (StatisticsItem feedTime : statisticsResult.feedTime) {
totalTime += feedTime.timePlayed;
@ -77,7 +65,7 @@ public class EchoSection extends Fragment {
void hideThisYear() {
getContext().getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE)
.edit().putInt(HomeFragment.PREF_HIDE_ECHO, EchoActivity.RELEASE_YEAR).apply();
.edit().putInt(HomeFragment.PREF_HIDE_ECHO, EchoConfig.RELEASE_YEAR).apply();
((MainActivity) getActivity()).loadFragment(HomeFragment.TAG, null);
}
}

View File

@ -26,7 +26,7 @@
</Match>
<Match>
<Bug pattern="ICAST_IDIV_CAST_TO_DOUBLE"/>
<Class name="de.danoeh.antennapod.ui.echo.screens.RotatingSquaresScreen"/>
<Class name="de.danoeh.antennapod.ui.echo.background.RotatingSquaresBackground"/>
</Match>
<Match>
<Bug pattern="MS_CANNOT_BE_FINAL"/>

View File

@ -1,97 +1,60 @@
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.text.format.DateFormat;
import android.util.Log;
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.model.feed.FeedItem;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.StatisticsItem;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.ui.common.Converter;
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 de.danoeh.antennapod.ui.echo.screens.WavesScreen;
import de.danoeh.antennapod.ui.episodes.PlaybackSpeedUtils;
import de.danoeh.antennapod.ui.echo.screen.EchoScreen;
import de.danoeh.antennapod.ui.echo.screen.FinalShareScreen;
import de.danoeh.antennapod.ui.echo.screen.HoarderScreen;
import de.danoeh.antennapod.ui.echo.screen.HoursPlayedScreen;
import de.danoeh.antennapod.ui.echo.screen.IntroScreen;
import de.danoeh.antennapod.ui.echo.screen.QueueScreen;
import de.danoeh.antennapod.ui.echo.screen.ThanksScreen;
import de.danoeh.antennapod.ui.echo.screen.TimeReleasePlayScreen;
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 {
public static final int RELEASE_YEAR = 2023;
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 int currentScreenIdx = -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 Disposable disposableFavorite;
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<String> favoritePodNames = new ArrayList<>();
private final ArrayList<Drawable> favoritePodImages = new ArrayList<>();
private List<EchoScreen> screens;
private EchoScreen currentScreen;
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
super.onCreate(savedInstanceState);
screens = List.of(new IntroScreen(this, getLayoutInflater()),
new HoursPlayedScreen(this, getLayoutInflater()), new QueueScreen(this, getLayoutInflater()),
new TimeReleasePlayScreen(this, getLayoutInflater()), new HoarderScreen(this, getLayoutInflater()),
new ThanksScreen(this, getLayoutInflater()), new FinalShareScreen(this, getLayoutInflater()));
viewBinding = EchoActivityBinding.inflate(getLayoutInflater());
viewBinding.closeButton.setOnClickListener(v -> finish());
viewBinding.shareButton.setOnClickListener(v -> share());
viewBinding.echoImage.setOnTouchListener((v, event) -> {
viewBinding.screenContainer.setOnTouchListener((v, event) -> {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
progressPaused = true;
timeTouchDown = System.currentTimeMillis();
@ -99,11 +62,11 @@ public class EchoActivity extends AppCompatActivity {
progressPaused = false;
if (timeTouchDown + 500 > System.currentTimeMillis()) {
int newScreen;
if (event.getX() < 0.5f * viewBinding.echoImage.getMeasuredWidth()) {
newScreen = Math.max(currentScreen - 1, 0);
if (event.getX() < 0.5f * viewBinding.screenContainer.getMeasuredWidth()) {
newScreen = Math.max(currentScreenIdx - 1, 0);
} else {
newScreen = Math.min(currentScreen + 1, NUM_SCREENS - 1);
if (currentScreen == NUM_SCREENS - 1) {
newScreen = Math.min(currentScreenIdx + 1, NUM_SCREENS - 1);
if (currentScreenIdx == NUM_SCREENS - 1) {
finish();
}
}
@ -121,35 +84,9 @@ public class EchoActivity extends AppCompatActivity {
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, RELEASE_YEAR))
.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()
@ -157,7 +94,7 @@ public class EchoActivity extends AppCompatActivity {
if (progressPaused) {
return;
}
viewBinding.echoImage.postInvalidate();
currentScreen.postInvalidate();
if (progress >= NUM_SCREENS - 0.001f) {
return;
}
@ -180,254 +117,38 @@ public class EchoActivity extends AppCompatActivity {
if (disposable != null) {
disposable.dispose();
}
if (disposableFavorite != null) {
disposableFavorite.dispose();
}
}
private void loadScreen(int screen, boolean force) {
if (screen == currentScreen && !force) {
if (screen == currentScreenIdx && !force) {
return;
}
currentScreen = screen;
currentScreenIdx = screen;
currentScreen = screens.get(currentScreenIdx);
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", RELEASE_YEAR));
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));
Calendar dec31 = Calendar.getInstance();
dec31.set(Calendar.DAY_OF_MONTH, 31);
dec31.set(Calendar.MONTH, Calendar.DECEMBER);
int daysUntilNextYear = Math.max(1,
dec31.get(Calendar.DAY_OF_YEAR) - Calendar.getInstance().get(Calendar.DAY_OF_YEAR) + 1);
long secondsPerDay = queueSecondsLeft / daysUntilNextYear;
String timePerDay = Converter.getDurationStringLocalized(
getLocalizedResources(this, getEchoLanguage()), secondsPerDay * 1000, true);
double hoursPerDay = secondsPerDay / 3600.0;
int nextYear = RELEASE_YEAR + 1;
if (hoursPerDay < 1.5) {
viewBinding.aboveLabel.setText(R.string.echo_queue_title_clean);
viewBinding.smallLabel.setText(
getString(R.string.echo_queue_hours_clean, timePerDay, nextYear));
} else if (hoursPerDay <= 24) {
viewBinding.aboveLabel.setText(R.string.echo_queue_title_many);
viewBinding.smallLabel.setText(
getString(R.string.echo_queue_hours_normal, timePerDay, nextYear));
} else {
viewBinding.aboveLabel.setText(R.string.echo_queue_title_many);
viewBinding.smallLabel.setText(getString(R.string.echo_queue_hours_much, timePerDay, nextYear));
}
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, true)));
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 WavesScreen(this);
break;
case 5:
viewBinding.aboveLabel.setText("");
viewBinding.largeLabel.setText(R.string.echo_thanks_large);
if (oldestDate < jan1()) {
String skeleton = DateFormat.getBestDateTimePattern(getEchoLanguage(), "MMMM yyyy");
SimpleDateFormat dateFormat = new SimpleDateFormat(skeleton, 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, favoritePodNames, favoritePodImages);
break;
default: // Keep
}
viewBinding.echoImage.setImageDrawable(currentDrawable);
viewBinding.screenContainer.removeAllViews();
viewBinding.screenContainer.addView(currentScreen.getView());
});
}
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, RELEASE_YEAR);
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);
false, EchoConfig.jan1(), Long.MAX_VALUE);
Collections.sort(statisticsData.feedTime, (item1, item2) ->
Long.compare(item2.timePlayed, item1.timePlayed));
favoritePodNames.clear();
for (int i = 0; i < 5 && i < statisticsData.feedTime.size(); i++) {
favoritePodNames.add(statisticsData.feedTime.get(i).feed.getTitle());
}
loadFavoritePodImages(statisticsData);
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)));
}
void loadFavoritePodImages(DBReader.StatisticsResult statisticsData) {
if (disposableFavorite != null) {
disposableFavorite.dispose();
}
disposableFavorite = Observable.fromCallable(
() -> {
favoritePodImages.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(5, TimeUnit.SECONDS));
} catch (Exception e) {
Log.d(TAG, "Loading cover: " + e.getMessage());
}
favoritePodImages.add(cover);
.subscribe(result -> {
for (EchoScreen screen : screens) {
screen.startLoading(result);
}
return statisticsData;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> { },
error -> Log.e(TAG, Log.getStackTraceString(error)));
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
}

View File

@ -0,0 +1,19 @@
package de.danoeh.antennapod.ui.echo;
import java.util.Calendar;
public class EchoConfig {
public static final int RELEASE_YEAR = 2023;
public static 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, RELEASE_YEAR);
return date.getTimeInMillis();
}
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.ui.echo.screens;
package de.danoeh.antennapod.ui.echo.background;
import android.content.Context;
import android.graphics.Canvas;
@ -14,7 +14,7 @@ import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import de.danoeh.antennapod.ui.echo.R;
public abstract class BaseScreen extends Drawable {
public abstract class BaseBackground extends Drawable {
private final Paint paintBackground;
protected final Paint paintParticles;
protected final ArrayList<Particle> particles = new ArrayList<>();
@ -22,7 +22,7 @@ public abstract class BaseScreen extends Drawable {
private final int colorBackgroundTo;
private long lastFrame = 0;
public BaseScreen(Context context) {
public BaseBackground(Context context) {
colorBackgroundFrom = ContextCompat.getColor(context, R.color.gradient_000);
colorBackgroundTo = ContextCompat.getColor(context, R.color.gradient_100);
paintBackground = new Paint();

View File

@ -1,14 +1,14 @@
package de.danoeh.antennapod.ui.echo.screens;
package de.danoeh.antennapod.ui.echo.background;
import android.content.Context;
import android.graphics.Canvas;
import androidx.annotation.NonNull;
public class BubbleScreen extends BaseScreen {
public class BubbleBackground extends BaseBackground {
protected static final double PARTICLE_SPEED = 0.00002;
protected static final int NUM_PARTICLES = 15;
public BubbleScreen(Context context) {
public BubbleBackground(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

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.ui.echo.screens;
package de.danoeh.antennapod.ui.echo.background;
import android.content.Context;
import android.graphics.Canvas;
@ -9,11 +9,11 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.res.ResourcesCompat;
import de.danoeh.antennapod.ui.echo.EchoActivity;
import de.danoeh.antennapod.ui.echo.EchoConfig;
import de.danoeh.antennapod.ui.echo.R;
import java.util.ArrayList;
public class FinalShareScreen extends BubbleScreen {
public class FinalShareBackground extends BubbleBackground {
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;
@ -26,14 +26,14 @@ public class FinalShareScreen extends BubbleScreen {
private final Typeface typefaceNormal;
private final Typeface typefaceBold;
public FinalShareScreen(Context context,
ArrayList<String> favoritePodNames, ArrayList<Drawable> favoritePodImages) {
public FinalShareBackground(Context context,
ArrayList<String> favoritePodNames, ArrayList<Drawable> favoritePodImages) {
super(context);
this.heading = context.getString(R.string.echo_share_heading);
this.logo = AppCompatResources.getDrawable(context, R.drawable.echo);
this.favoritePodNames = favoritePodNames;
this.favoritePodImages = favoritePodImages;
this.year = String.valueOf(EchoActivity.RELEASE_YEAR);
this.year = String.valueOf(EchoConfig.RELEASE_YEAR);
typefaceNormal = ResourcesCompat.getFont(context, R.font.sarabun_regular);
typefaceBold = ResourcesCompat.getFont(context, R.font.sarabun_semi_bold);
paintTextMain = new Paint();

View File

@ -1,11 +1,11 @@
package de.danoeh.antennapod.ui.echo.screens;
package de.danoeh.antennapod.ui.echo.background;
import android.content.Context;
import android.graphics.Canvas;
import androidx.annotation.NonNull;
public class RotatingSquaresScreen extends BaseScreen {
public RotatingSquaresScreen(Context context) {
public class RotatingSquaresBackground extends BaseBackground {
public RotatingSquaresBackground(Context context) {
super(context);
for (int i = 0; i < 16; i++) {
particles.add(new Particle(

View File

@ -1,13 +1,13 @@
package de.danoeh.antennapod.ui.echo.screens;
package de.danoeh.antennapod.ui.echo.background;
import android.content.Context;
import android.graphics.Canvas;
import androidx.annotation.NonNull;
public class StripesScreen extends BaseScreen {
public class StripesBackground extends BaseBackground {
protected static final int NUM_PARTICLES = 15;
public StripesScreen(Context context) {
public StripesBackground(Context context) {
super(context);
for (int i = 0; i < NUM_PARTICLES; i++) {
particles.add(new Particle(2f * i / NUM_PARTICLES - 1f, 0, 0, 0));

View File

@ -1,13 +1,13 @@
package de.danoeh.antennapod.ui.echo.screens;
package de.danoeh.antennapod.ui.echo.background;
import android.content.Context;
import android.graphics.Canvas;
import androidx.annotation.NonNull;
public class WaveformScreen extends BaseScreen {
public class WaveformBackground extends BaseBackground {
protected static final int NUM_PARTICLES = 40;
public WaveformScreen(Context context) {
public WaveformBackground(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));

View File

@ -1,14 +1,14 @@
package de.danoeh.antennapod.ui.echo.screens;
package de.danoeh.antennapod.ui.echo.background;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import androidx.annotation.NonNull;
public class WavesScreen extends BaseScreen {
public class WavesBackground extends BaseBackground {
protected static final int NUM_PARTICLES = 10;
public WavesScreen(Context context) {
public WavesBackground(Context context) {
super(context);
paintParticles.setStyle(Paint.Style.STROKE);
for (int i = 0; i < NUM_PARTICLES; i++) {

View File

@ -0,0 +1,48 @@
package de.danoeh.antennapod.ui.echo.screen;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.view.View;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.ui.echo.R;
import java.util.Locale;
public abstract class EchoScreen {
protected final Context context;
public EchoScreen(Context context) {
this.context = context;
}
protected final Locale getEchoLanguage() {
boolean hasTranslation = !context.getString(R.string.echo_listened_after_title)
.equals(getLocalizedResources(Locale.US).getString(R.string.echo_listened_after_title));
if (hasTranslation) {
return Locale.getDefault();
} else {
return Locale.US;
}
}
@NonNull
protected Resources getLocalizedResources(Locale desiredLocale) {
Configuration conf = context.getResources().getConfiguration();
conf = new Configuration(conf);
conf.setLocale(desiredLocale);
Context localizedContext = context.createConfigurationContext(conf);
return localizedContext.getResources();
}
public void postInvalidate() {
// Do nothing by default
}
public abstract View getView();
public void startLoading(DBReader.StatisticsResult statisticsResult) {
// Do nothing by default
}
}

View File

@ -0,0 +1,127 @@
package de.danoeh.antennapod.ui.echo.screen;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import androidx.core.app.ShareCompat;
import androidx.core.content.FileProvider;
import androidx.core.content.res.ResourcesCompat;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.ui.echo.EchoConfig;
import de.danoeh.antennapod.ui.echo.R;
import de.danoeh.antennapod.ui.echo.background.FinalShareBackground;
import de.danoeh.antennapod.ui.echo.databinding.SimpleEchoScreenBinding;
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.util.ArrayList;
import java.util.concurrent.TimeUnit;
public class FinalShareScreen extends EchoScreen {
private static final int SHARE_SIZE = 1000;
private static final String TAG = "FinalShareScreen";
private final SimpleEchoScreenBinding viewBinding;
private final ArrayList<String> favoritePodNames = new ArrayList<>();
private final ArrayList<Drawable> favoritePodImages = new ArrayList<>();
private final FinalShareBackground background;
private Disposable disposable;
public FinalShareScreen(Context context, LayoutInflater layoutInflater) {
super(context);
viewBinding = SimpleEchoScreenBinding.inflate(layoutInflater);
viewBinding.actionButton.setOnClickListener(v -> share());
viewBinding.actionButton.setCompoundDrawablesWithIntrinsicBounds(ResourcesCompat.getDrawable(
context.getResources(), R.drawable.ic_share, context.getTheme()), null, null, null);
viewBinding.actionButton.setVisibility(View.VISIBLE);
viewBinding.actionButton.setText(R.string.share_label);
background = new FinalShareBackground(context, favoritePodNames, favoritePodImages);
viewBinding.backgroundImage.setImageDrawable(background);
}
@Override
public View getView() {
return viewBinding.getRoot();
}
@Override
public void postInvalidate() {
viewBinding.backgroundImage.postInvalidate();
}
private void share() {
try {
Bitmap bitmap = Bitmap.createBitmap(SHARE_SIZE, SHARE_SIZE, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
background.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
background.draw(canvas);
viewBinding.backgroundImage.setImageDrawable(null);
viewBinding.backgroundImage.setImageDrawable(background);
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(context, context.getString(R.string.provider_authority), file);
new ShareCompat.IntentBuilder(context)
.setType("image/png")
.addStream(fileUri)
.setText(context.getString(R.string.echo_share, EchoConfig.RELEASE_YEAR))
.setChooserTitle(R.string.share_file_label)
.startChooser();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void startLoading(DBReader.StatisticsResult statisticsData) {
favoritePodNames.clear();
for (int i = 0; i < 5 && i < statisticsData.feedTime.size(); i++) {
favoritePodNames.add(statisticsData.feedTime.get(i).feed.getTitle());
}
if (disposable != null) {
disposable.dispose();
}
disposable = Observable.fromCallable(
() -> {
favoritePodImages.clear();
for (int i = 0; i < 5 && i < statisticsData.feedTime.size(); i++) {
BitmapDrawable cover = new BitmapDrawable(context.getResources(), (Bitmap) null);
try {
final int size = SHARE_SIZE / 3;
final int radius = (i == 0) ? (size / 16) : (size / 8);
cover = new BitmapDrawable(context.getResources(), Glide.with(context)
.asBitmap()
.load(statisticsData.feedTime.get(i).feed.getImageUrl())
.apply(new RequestOptions()
.fitCenter()
.transform(new RoundedCorners(radius)))
.submit(size, size)
.get(5, TimeUnit.SECONDS));
} catch (Exception e) {
Log.d(TAG, "Loading cover: " + e.getMessage());
}
favoritePodImages.add(cover);
}
return statisticsData;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> { },
error -> Log.e(TAG, Log.getStackTraceString(error)));
}
}

View File

@ -0,0 +1,75 @@
package de.danoeh.antennapod.ui.echo.screen;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.StatisticsItem;
import de.danoeh.antennapod.ui.echo.R;
import de.danoeh.antennapod.ui.echo.background.WavesBackground;
import de.danoeh.antennapod.ui.echo.databinding.SimpleEchoScreenBinding;
import java.util.ArrayList;
public class HoarderScreen extends EchoScreen {
private final SimpleEchoScreenBinding viewBinding;
public HoarderScreen(Context context, LayoutInflater layoutInflater) {
super(context);
viewBinding = SimpleEchoScreenBinding.inflate(layoutInflater);
viewBinding.aboveLabel.setText(R.string.echo_hoarder_title);
viewBinding.backgroundImage.setImageDrawable(new WavesBackground(context));
}
private void display(int playedActivePodcasts, int totalActivePodcasts, String randomUnplayedActivePodcast) {
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(context.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(context.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(context.getString(R.string.echo_hoarder_comment_clean,
percentagePlayed, totalActivePodcasts));
}
}
@Override
public View getView() {
return viewBinding.getRoot();
}
@Override
public void postInvalidate() {
viewBinding.backgroundImage.postInvalidate();
}
@Override
public void startLoading(DBReader.StatisticsResult statisticsResult) {
int totalActivePodcasts = 0;
int playedActivePodcasts = 0;
String randomUnplayedActivePodcast = "";
ArrayList<String> unplayedActive = new ArrayList<>();
for (StatisticsItem item : statisticsResult.feedTime) {
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()));
}
display(playedActivePodcasts, totalActivePodcasts, randomUnplayedActivePodcast);
}
}

View File

@ -0,0 +1,50 @@
package de.danoeh.antennapod.ui.echo.screen;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.StatisticsItem;
import de.danoeh.antennapod.ui.echo.R;
import de.danoeh.antennapod.ui.echo.background.WaveformBackground;
import de.danoeh.antennapod.ui.echo.databinding.SimpleEchoScreenBinding;
public class HoursPlayedScreen extends EchoScreen {
private final SimpleEchoScreenBinding viewBinding;
public HoursPlayedScreen(Context context, LayoutInflater layoutInflater) {
super(context);
viewBinding = SimpleEchoScreenBinding.inflate(layoutInflater);
viewBinding.aboveLabel.setText(R.string.echo_hours_this_year);
viewBinding.backgroundImage.setImageDrawable(new WaveformBackground(context));
}
private void display(long totalTime, int playedPodcasts) {
viewBinding.largeLabel.setText(String.format(getEchoLanguage(), "%d", totalTime / 3600));
viewBinding.belowLabel.setText(context.getResources()
.getQuantityString(R.plurals.echo_hours_podcasts, playedPodcasts, playedPodcasts));
}
@Override
public View getView() {
return viewBinding.getRoot();
}
@Override
public void postInvalidate() {
viewBinding.backgroundImage.postInvalidate();
}
@Override
public void startLoading(DBReader.StatisticsResult statisticsResult) {
int playedPodcasts = 0;
long totalTime = 0;
for (StatisticsItem item : statisticsResult.feedTime) {
totalTime += item.timePlayed;
if (item.timePlayed > 0) {
playedPodcasts++;
}
}
display(totalTime, playedPodcasts);
}
}

View File

@ -0,0 +1,34 @@
package de.danoeh.antennapod.ui.echo.screen;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import de.danoeh.antennapod.ui.echo.EchoConfig;
import de.danoeh.antennapod.ui.echo.R;
import de.danoeh.antennapod.ui.echo.background.BubbleBackground;
import de.danoeh.antennapod.ui.echo.databinding.SimpleEchoScreenBinding;
public class IntroScreen extends EchoScreen {
private final SimpleEchoScreenBinding viewBinding;
public IntroScreen(Context context, LayoutInflater layoutInflater) {
super(context);
viewBinding = SimpleEchoScreenBinding.inflate(layoutInflater);
viewBinding.echoLogo.setVisibility(View.VISIBLE);
viewBinding.aboveLabel.setText(R.string.echo_intro_your_year);
viewBinding.largeLabel.setText(String.format(getEchoLanguage(), "%d", EchoConfig.RELEASE_YEAR));
viewBinding.belowLabel.setText(R.string.echo_intro_in_podcasts);
viewBinding.smallLabel.setText(R.string.echo_intro_locally);
viewBinding.backgroundImage.setImageDrawable(new BubbleBackground(context));
}
@Override
public View getView() {
return viewBinding.getRoot();
}
@Override
public void postInvalidate() {
viewBinding.backgroundImage.postInvalidate();
}
}

View File

@ -0,0 +1,97 @@
package de.danoeh.antennapod.ui.echo.screen;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.ui.common.Converter;
import de.danoeh.antennapod.ui.echo.EchoConfig;
import de.danoeh.antennapod.ui.echo.R;
import de.danoeh.antennapod.ui.echo.background.StripesBackground;
import de.danoeh.antennapod.ui.echo.databinding.SimpleEchoScreenBinding;
import de.danoeh.antennapod.ui.episodes.PlaybackSpeedUtils;
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 QueueScreen extends EchoScreen {
private static final String TAG = "QueueScreen";
private final SimpleEchoScreenBinding viewBinding;
private Disposable disposable;
public QueueScreen(Context context, LayoutInflater layoutInflater) {
super(context);
viewBinding = SimpleEchoScreenBinding.inflate(layoutInflater);
viewBinding.backgroundImage.setImageDrawable(new StripesBackground(context));
}
private void display(int queueNumEpisodes, long queueSecondsLeft) {
viewBinding.largeLabel.setText(String.format(getEchoLanguage(), "%d", queueSecondsLeft / 3600));
viewBinding.belowLabel.setText(context.getResources().getQuantityString(
R.plurals.echo_queue_hours_waiting, queueNumEpisodes, queueNumEpisodes));
Calendar dec31 = Calendar.getInstance();
dec31.set(Calendar.DAY_OF_MONTH, 31);
dec31.set(Calendar.MONTH, Calendar.DECEMBER);
int daysUntilNextYear = Math.max(1,
dec31.get(Calendar.DAY_OF_YEAR) - Calendar.getInstance().get(Calendar.DAY_OF_YEAR) + 1);
long secondsPerDay = queueSecondsLeft / daysUntilNextYear;
String timePerDay = Converter.getDurationStringLocalized(
getLocalizedResources(getEchoLanguage()), secondsPerDay * 1000, true);
double hoursPerDay = secondsPerDay / 3600.0;
int nextYear = EchoConfig.RELEASE_YEAR + 1;
if (hoursPerDay < 1.5) {
viewBinding.aboveLabel.setText(R.string.echo_queue_title_clean);
viewBinding.smallLabel.setText(
context.getString(R.string.echo_queue_hours_clean, timePerDay, nextYear));
} else if (hoursPerDay <= 24) {
viewBinding.aboveLabel.setText(R.string.echo_queue_title_many);
viewBinding.smallLabel.setText(
context.getString(R.string.echo_queue_hours_normal, timePerDay, nextYear));
} else {
viewBinding.aboveLabel.setText(R.string.echo_queue_title_many);
viewBinding.smallLabel.setText(context.getString(R.string.echo_queue_hours_much, timePerDay, nextYear));
}
}
@Override
public View getView() {
return viewBinding.getRoot();
}
@Override
public void postInvalidate() {
viewBinding.backgroundImage.postInvalidate();
}
@Override
public void startLoading(DBReader.StatisticsResult statisticsResult) {
if (disposable != null) {
disposable.dispose();
}
disposable = Observable.fromCallable(DBReader::getQueue)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(queue -> {
long 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;
display(queue.size(), queueSecondsLeft);
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
}

View File

@ -0,0 +1,50 @@
package de.danoeh.antennapod.ui.echo.screen;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.ui.echo.EchoConfig;
import de.danoeh.antennapod.ui.echo.R;
import de.danoeh.antennapod.ui.echo.background.RotatingSquaresBackground;
import de.danoeh.antennapod.ui.echo.databinding.SimpleEchoScreenBinding;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThanksScreen extends EchoScreen {
private final SimpleEchoScreenBinding viewBinding;
public ThanksScreen(Context context, LayoutInflater layoutInflater) {
super(context);
viewBinding = SimpleEchoScreenBinding.inflate(layoutInflater);
viewBinding.aboveLabel.setText("");
viewBinding.largeLabel.setText(R.string.echo_thanks_large);
viewBinding.smallLabel.setText(R.string.echo_thanks_now_favorite);
viewBinding.backgroundImage.setImageDrawable(new RotatingSquaresBackground(context));
}
@Override
public View getView() {
return viewBinding.getRoot();
}
@Override
public void postInvalidate() {
viewBinding.backgroundImage.postInvalidate();
}
@Override
public void startLoading(DBReader.StatisticsResult statisticsResult) {
if (statisticsResult.oldestDate < EchoConfig.jan1()) {
String skeleton = DateFormat.getBestDateTimePattern(getEchoLanguage(), "MMMM yyyy");
SimpleDateFormat dateFormat = new SimpleDateFormat(skeleton, getEchoLanguage());
String dateFrom = dateFormat.format(new Date(statisticsResult.oldestDate));
viewBinding.belowLabel.setText(context.getString(R.string.echo_thanks_we_are_glad_old, dateFrom));
} else {
viewBinding.belowLabel.setText(R.string.echo_thanks_we_are_glad_new);
}
}
}

View File

@ -0,0 +1,65 @@
package de.danoeh.antennapod.ui.echo.screen;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.ui.common.Converter;
import de.danoeh.antennapod.ui.echo.EchoConfig;
import de.danoeh.antennapod.ui.echo.R;
import de.danoeh.antennapod.ui.echo.background.RotatingSquaresBackground;
import de.danoeh.antennapod.ui.echo.databinding.SimpleEchoScreenBinding;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class TimeReleasePlayScreen extends EchoScreen {
private static final String TAG = "TimeReleasePlayScreen";
private final SimpleEchoScreenBinding viewBinding;
private Disposable disposable;
public TimeReleasePlayScreen(Context context, LayoutInflater layoutInflater) {
super(context);
viewBinding = SimpleEchoScreenBinding.inflate(layoutInflater);
viewBinding.aboveLabel.setText(R.string.echo_listened_after_title);
viewBinding.backgroundImage.setImageDrawable(new RotatingSquaresBackground(context));
}
private void display(long timeBetweenReleaseAndPlay) {
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(context.getString(R.string.echo_listened_after_time,
Converter.getDurationStringLocalized(
getLocalizedResources(getEchoLanguage()), timeBetweenReleaseAndPlay, true)));
}
@Override
public View getView() {
return viewBinding.getRoot();
}
@Override
public void postInvalidate() {
viewBinding.backgroundImage.postInvalidate();
}
@Override
public void startLoading(DBReader.StatisticsResult statisticsResult) {
super.startLoading(statisticsResult);
if (disposable != null) {
disposable.dispose();
}
disposable = Observable.fromCallable(() ->
DBReader.getTimeBetweenReleaseAndPlayback(EchoConfig.jan1(), Long.MAX_VALUE))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::display, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
}

View File

@ -1,16 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<FrameLayout
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"
<FrameLayout
android:id="@+id/screenContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no" />
android:layout_height="match_parent" />
<RelativeLayout
android:layout_width="match_parent"
@ -49,87 +46,6 @@
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>
</FrameLayout>

View File

@ -0,0 +1,101 @@
<?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/backgroundImage"
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">
<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:src="@drawable/echo"
android:layout_margin="32dp"
android:importantForAccessibility="no"
android:layout_alignParentBottom="true" />
<com.google.android.material.button.MaterialButton
android:id="@+id/actionButton"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:visibility="gone"
android:layout_margin="32dp"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:textColor="#fff"
style="@style/Widget.Material3.Button.OutlinedButton"
app:strokeColor="#fff"
tools:ignore="RtlHardcoded" />
</RelativeLayout>
</RelativeLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation,PluralsCandidate">
<string name="debug_echo" translatable="false">[Debug] Echo</string>
<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>

View File

@ -15,6 +15,7 @@ dependencies {
implementation project(':storage:database')
implementation project(":ui:app-start-intent")
implementation project(":ui:common")
implementation project(":ui:echo")
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"

View File

@ -2,6 +2,7 @@ package de.danoeh.antennapod.ui.statistics;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
@ -21,6 +22,7 @@ import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.event.StatisticsEvent;
import de.danoeh.antennapod.ui.common.PagedToolbarFragment;
import de.danoeh.antennapod.ui.echo.EchoActivity;
import de.danoeh.antennapod.ui.statistics.downloads.DownloadStatisticsFragment;
import de.danoeh.antennapod.ui.statistics.subscriptions.SubscriptionStatisticsFragment;
import de.danoeh.antennapod.ui.statistics.years.YearsStatisticsFragment;
@ -61,6 +63,9 @@ public class StatisticsFragment extends PagedToolbarFragment {
toolbar = rootView.findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.statistics_label));
toolbar.inflateMenu(R.menu.statistics);
if (BuildConfig.DEBUG) {
toolbar.getMenu().findItem(R.id.debug_echo).setVisible(true);
}
toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
viewPager.setAdapter(new StatisticsPagerAdapter(this));
// Give the TabLayout the ViewPager
@ -89,6 +94,8 @@ public class StatisticsFragment extends PagedToolbarFragment {
if (item.getItemId() == R.id.statistics_reset) {
confirmResetStatistics();
return true;
} else if (item.getItemId() == R.id.debug_echo) {
startActivity(new Intent(getContext(), EchoActivity.class));
}
return super.onOptionsItemSelected(item);
}

View File

@ -11,7 +11,12 @@
android:id="@+id/statistics_filter"
android:icon="@drawable/ic_filter"
android:title="@string/filter"
custom:showAsAction="ifRoom">
</item>
custom:showAsAction="ifRoom" />
<item
android:id="@+id/debug_echo"
android:title="@string/debug_echo"
custom:showAsAction="never"
android:visible="false" />
</menu>