Add UnreachableActivity for when the server cannot be reached.

This commit is contained in:
天クマ 2026-01-23 16:04:27 -03:00
commit 34c4bcc831
28 changed files with 279 additions and 39 deletions

View file

@ -66,7 +66,7 @@
android:name="org.adrianvictor.geleia.views.shortcuts.AppShortcutLauncherActivity"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<activity android:name="org.adrianvictor.geleia.activities.UnreachableActivity" />
<service android:name="org.adrianvictor.geleia.service.DownloadService" />
<service android:name="org.adrianvictor.geleia.service.LoginService" />
<service android:name="org.adrianvictor.geleia.service.MusicService" />

View file

@ -40,7 +40,7 @@ public class App extends Application {
database = createDatabase(this);
apiClient = createApiClient(this);
if (database.userDao().getUsers().size() == 0) {
if (database.userDao().getUsers().isEmpty()) {
PreferenceUtil.getInstance(this).setServer(null);
PreferenceUtil.getInstance(this).setUser(null);
}
@ -76,6 +76,7 @@ public class App extends Application {
IDevice device = new AndroidDevice(deviceId, deviceName);
EventListener eventListener = new EventListener();
return new ApiClient(httpClient, logger, server, appName, appVersion, device, eventListener);
}

View file

@ -1,9 +1,12 @@
package org.adrianvictor.geleia.activities;
import static org.adrianvictor.geleia.adapter.CustomFragmentStatePagerAdapter.TAG;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@ -13,10 +16,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.lifecycle.Lifecycle;
import com.afollestad.materialcab.attached.AttachedCab;
import com.afollestad.materialcab.attached.AttachedCabKt;
import org.adrianvictor.geleia.activities.base.AbsMusicContentActivity;
import org.adrianvictor.geleia.fragments.OfflineFragment;
import org.adrianvictor.geleia.interfaces.CabHolder;
import org.adrianvictor.geleia.util.PreferenceUtil;
import org.adrianvictor.geleia.util.ThemeUtil;
@ -42,6 +47,7 @@ public class MainActivity extends AbsMusicContentActivity implements CabHolder {
private ActivityMainContentBinding contentBinding;
private NavigationDrawerHeaderBinding navigationBinding;
private boolean onLogout;
private boolean pendingShowOffline = false;
@Nullable
private AttachedCab cab;
@ -97,6 +103,24 @@ public class MainActivity extends AbsMusicContentActivity implements CabHolder {
});
}
@Override
public void onStateOffline() {
Log.d(TAG, "onStateOffline() foi chamado.");
Menu menu = binding.navigationView.getMenu();
menu.clear();
menu.add(R.id.navigation_drawer_menu_category_other, R.id.nav_settings, menu.size(), R.string.action_settings);
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_settings_white_24dp);
menu.add(R.id.navigation_drawer_menu_category_other, R.id.nav_about, menu.size(), R.string.action_about);
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_info_outline_white_24dp);
menu.add(R.id.navigation_drawer_menu_category_other, R.id.nav_logout, menu.size(), R.string.logout);
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_exit_to_app_white_48dp);
setUpDrawerLayout();
pendingShowOffline = true;
}
@Override
public void onPause() {
super.onPause();
@ -108,6 +132,16 @@ public class MainActivity extends AbsMusicContentActivity implements CabHolder {
}
}
@Override
protected void onResume() {
super.onResume();
if (pendingShowOffline) {
setCurrentFragment(OfflineFragment.newInstance());
pendingShowOffline = false;
}
}
private void setCurrentFragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, null).commit();
}

View file

@ -93,6 +93,9 @@ public class SearchActivity extends AbsMusicContentActivity implements SearchVie
public void onStateOnline() {
}
@Override
public void onStateOffline() {}
private void setUpToolBar() {
binding.toolbar.setBackgroundColor(PreferenceUtil.getInstance(this).getPrimaryColor());
setSupportActionBar(binding.toolbar);

View file

@ -1,8 +1,13 @@
package org.adrianvictor.geleia.activities;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import androidx.annotation.NonNull;
import org.adrianvictor.geleia.App;
import org.adrianvictor.geleia.R;
@ -15,6 +20,27 @@ import org.adrianvictor.geleia.util.PreferenceUtil;
import java.util.List;
public class SplashActivity extends AbsBaseActivity {
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, @NonNull Intent intent) {
if (intent.getAction() == null) {
return;
}
switch (intent.getAction()) {
case LoginService.STATE_ONLINE:
NavigationUtil.startMain(context);
finish();
break;
case LoginService.STATE_OFFLINE:
NavigationUtil.startUnreachable(context);
finish();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -25,6 +51,7 @@ public class SplashActivity extends AbsBaseActivity {
@Override
public void onPause() {
super.onPause();
unregisterReceiver(receiver);
overridePendingTransition(0, R.anim.fade_delay);
}
@ -32,16 +59,27 @@ public class SplashActivity extends AbsBaseActivity {
protected void onResume() {
super.onResume();
final IntentFilter filter = new IntentFilter();
filter.addAction(LoginService.STATE_ONLINE);
filter.addAction(LoginService.STATE_OFFLINE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
registerReceiver(receiver, filter);
}
User user = App.getDatabase().userDao().getUser(PreferenceUtil.getInstance(this).getUser());
List<User> available = App.getDatabase().userDao().getUsers();
if (user == null && available.size() != 0) {
if (user == null && !available.isEmpty()) {
NavigationUtil.startSelect(this);
finish();
} else if (user == null) {
NavigationUtil.startLogin(this);
finish();
} else {
startService(new Intent(this, LoginService.class));
new Handler().postDelayed(() -> NavigationUtil.startMain(this), 1000);
}
}
}

View file

@ -0,0 +1,21 @@
package org.adrianvictor.geleia.activities;
import android.os.Bundle;
import android.view.View;
import org.adrianvictor.geleia.R;
import org.adrianvictor.geleia.activities.base.AbsThemeActivity;
import org.adrianvictor.geleia.util.NavigationUtil;
public class UnreachableActivity extends AbsThemeActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_unreachable);
}
public void onSelectClick(View view) {
NavigationUtil.startSelect(this);
}
}

View file

@ -59,7 +59,7 @@ public abstract class AbsBaseActivity extends AbsThemeActivity {
.setPositiveButton(R.string.disable, (dialog, id) -> requestBatteryOptimization());
new Handler().postDelayed(builder::show, 2000);
} else if (permissions.size() != 0 && ActivityCompat.shouldShowRequestPermissionRationale(this, permissions.get(0))) {
} else if (!permissions.isEmpty() && ActivityCompat.shouldShowRequestPermissionRationale(this, permissions.get(0))) {
builder.setMessage(getPermissionMessage())
.setTitle(R.string.permissions_denied)
.setPositiveButton(R.string.action_grant, (dialog, id) -> requestPermissions());

View file

@ -1,14 +1,19 @@
package org.adrianvictor.geleia.activities.base;
import static org.adrianvictor.geleia.adapter.CustomFragmentStatePagerAdapter.TAG;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import org.adrianvictor.geleia.App;
import org.adrianvictor.geleia.fragments.OfflineFragment;
import org.adrianvictor.geleia.interfaces.StateListener;
import org.adrianvictor.geleia.service.LoginService;
import org.adrianvictor.geleia.util.NavigationUtil;
@ -24,7 +29,7 @@ public abstract class AbsMusicContentActivity extends AbsMusicPanelActivity impl
onStateOnline();
break;
case LoginService.STATE_OFFLINE:
NavigationUtil.startLogin(context);
NavigationUtil.startSelect(context);
break;
}
}
@ -39,7 +44,11 @@ public abstract class AbsMusicContentActivity extends AbsMusicPanelActivity impl
filter.addAction(LoginService.STATE_ONLINE);
filter.addAction(LoginService.STATE_OFFLINE);
registerReceiver(receiver, filter);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
registerReceiver(receiver, filter);
}
if (App.getApiClient() == null) {
startService(new Intent(this, LoginService.class));
@ -51,24 +60,14 @@ public abstract class AbsMusicContentActivity extends AbsMusicPanelActivity impl
@Override
protected void onResume() {
super.onResume();
if (App.getApiClient() == null) {
startService(new Intent(this, LoginService.class));
}
}
@Override
protected void onDestroy() {
unregisterReceiver(receiver);
super.onDestroy();
}
@Override
public void onStatePolling() {
}
@Override
public void onStateOffline() {
}
public void onStatePolling() {}
}

View file

@ -74,6 +74,11 @@ public class AlbumDetailActivity extends AbsMusicContentActivity implements Pale
});
}
@Override
public void onStateOffline() {
}
@Override
public void onOffsetChanged (AppBarLayout appBarLayout, int verticalOffset) {
float headerAlpha = Math.max(0, Math.min(1, 1 + (2 * (float) verticalOffset / headerViewHeight)));
@ -231,6 +236,6 @@ public class AlbumDetailActivity extends AbsMusicContentActivity implements Pale
binding.durationText.setText(MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(this, album.songs)));
binding.albumYearText.setText(MusicUtil.getYearString(album.year));
if (album.songs.size() != 0) adapter.swapDataSet(album.songs);
if (!album.songs.isEmpty()) adapter.swapDataSet(album.songs);
}
}

View file

@ -87,6 +87,11 @@ public class ArtistDetailActivity extends AbsMusicContentActivity implements Pal
});
}
@Override
public void onStateOffline() {
}
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
float headerAlpha = Math.max(0, Math.min(1, 1 + (2 * (float) verticalOffset / headerViewHeight)));
@ -258,7 +263,7 @@ public class ArtistDetailActivity extends AbsMusicContentActivity implements Pal
binding.albumCountText.setText(MusicUtil.getAlbumCountString(this, artist.albums.size()));
binding.durationText.setText(MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(this, artist.songs)));
if (artist.songs.size() != 0) songAdapter.swapDataSet(artist.songs);
if (artist.albums.size() != 0) albumAdapter.swapDataSet(artist.albums);
if (!artist.songs.isEmpty()) songAdapter.swapDataSet(artist.songs);
if (!artist.albums.isEmpty()) albumAdapter.swapDataSet(artist.albums);
}
}

View file

@ -59,6 +59,11 @@ public class GenreDetailActivity extends AbsMusicContentActivity implements CabH
});
}
@Override
public void onStateOffline() {
}
@Override
protected View createContentView() {
binding = ActivityGenreDetailBinding.inflate(getLayoutInflater());

View file

@ -71,6 +71,11 @@ public class PlaylistDetailActivity extends AbsMusicContentActivity implements C
});
}
@Override
public void onStateOffline() {
}
@Override
protected View createContentView() {
binding = ActivityPlaylistDetailBinding.inflate(getLayoutInflater());

View file

@ -180,7 +180,7 @@ public abstract class CustomFragmentStatePagerAdapter extends PagerAdapter {
@Override
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
if (!mSavedState.isEmpty()) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);

View file

@ -31,7 +31,7 @@ public class SongShareDialog extends DialogFragment {
final String currentlyListening = getString(R.string.currently_listening_to_x_by_x, song.title, song.artistName);
return new MaterialDialog.Builder(requireActivity())
.title(R.string.what_do_you_want_to_share)
.items(getString(R.string.the_audio_file), "\u201C" + currentlyListening + "\u201D")
.items(getString(R.string.the_audio_file), "" + currentlyListening + "")
.itemsCallback((materialDialog, view, i, charSequence) -> {
switch (i) {
case 0:

View file

@ -0,0 +1,25 @@
package org.adrianvictor.geleia.fragments;
import android.app.Activity;
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 org.adrianvictor.geleia.R;
public class OfflineFragment extends Fragment {
public static OfflineFragment newInstance() {
return new OfflineFragment();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_offline, container, false);
}
}

View file

@ -273,7 +273,7 @@ public class MusicPlayerRemote {
public static boolean playNext(Song song) {
if (musicService != null && musicService.queueManager != null) {
if (getPlayingQueue().size() > 0) {
if (!getPlayingQueue().isEmpty()) {
musicService.queueManager.addSong(getPosition() + 1, song);
} else {
List<Song> queue = new ArrayList<>();
@ -290,7 +290,7 @@ public class MusicPlayerRemote {
public static boolean playNext(@NonNull List<Song> songs) {
if (musicService != null && musicService.queueManager != null) {
if (getPlayingQueue().size() > 0) {
if (!getPlayingQueue().isEmpty()) {
musicService.queueManager.addSongs(getPosition() + 1, songs);
} else {
openQueue(songs, 0, false);
@ -306,7 +306,7 @@ public class MusicPlayerRemote {
public static boolean enqueue(Song song) {
if (musicService != null && musicService.queueManager != null) {
if (getPlayingQueue().size() > 0) {
if (!getPlayingQueue().isEmpty()) {
musicService.queueManager.addSong(song);
} else {
List<Song> queue = new ArrayList<>();
@ -323,7 +323,7 @@ public class MusicPlayerRemote {
public static boolean enqueue(@NonNull List<Song> songs) {
if (musicService != null && musicService.queueManager != null) {
if (getPlayingQueue().size() > 0) {
if (!getPlayingQueue().isEmpty()) {
musicService.queueManager.addSongs(songs);
} else {
openQueue(songs, 0, false);

View file

@ -29,10 +29,10 @@ public class Album implements Parcelable {
this.title = itemDto.getName();
this.year = itemDto.getProductionYear() != null ? itemDto.getProductionYear() : 0;
if (itemDto.getAlbumArtists().size() != 0) {
if (!itemDto.getAlbumArtists().isEmpty()) {
this.artistId = itemDto.getAlbumArtists().get(0).getId();
this.artistName = itemDto.getAlbumArtists().get(0).getName();
} else if (itemDto.getArtistItems().size() != 0) {
} else if (!itemDto.getArtistItems().isEmpty()) {
this.artistId = itemDto.getArtistItems().get(0).getId();
this.artistName = itemDto.getArtistItems().get(0).getName();
}

View file

@ -67,10 +67,10 @@ public class Song implements Parcelable {
this.albumId = itemDto.getAlbumId();
this.albumName = itemDto.getAlbum();
if (itemDto.getArtistItems().size() != 0) {
if (!itemDto.getArtistItems().isEmpty()) {
this.artistId = itemDto.getArtistItems().get(0).getId();
this.artistName = itemDto.getArtistItems().get(0).getName();
} else if (itemDto.getAlbumArtists().size() != 0) {
} else if (!itemDto.getAlbumArtists().isEmpty()) {
this.artistId = itemDto.getAlbumArtists().get(0).getId();
this.artistName = itemDto.getAlbumArtists().get(0).getName();
}
@ -93,7 +93,7 @@ public class Song implements Parcelable {
this.supportsTranscoding = source.getSupportsTranscoding();
if (source.getMediaStreams() != null && source.getMediaStreams().size() != 0) {
if (source.getMediaStreams() != null && !source.getMediaStreams().isEmpty()) {
MediaStream stream = source.getMediaStreams().get(0);
this.codec = stream.getCodec();

View file

@ -46,6 +46,21 @@ public class LoginService extends Service {
if (user == null) {
Toast.makeText(this, context.getResources().getString(R.string.error_unexpected), Toast.LENGTH_SHORT).show();
sendBroadcast(new Intent(STATE_OFFLINE));
return;
}
if (App.getApiClient() == null) {
try {
App.createApiClient(context);
} catch (Exception e) {
sendBroadcast(new Intent(STATE_OFFLINE));
return;
}
}
if (App.getApiClient() == null) {
sendBroadcast(new Intent(STATE_OFFLINE));
return;
}

View file

@ -83,7 +83,7 @@ public class DownloadNotification {
songs.clear();
}
if (songs.size() != 0) {
if (!songs.isEmpty()) {
return;
}

View file

@ -50,7 +50,7 @@ public class MusicUtil {
List<Codec> codecs = preferenceUtil.getDirectPlayCodecs();
Stream<String> values = codecs.stream().map(codec -> codec.value);
if (codecs.size() != 0) {
if (!codecs.isEmpty()) {
builder.append("&Container=").append(values.collect(Collectors.joining(",")));
}
@ -111,7 +111,7 @@ public class MusicUtil {
@NonNull
public static String getArtistInfoString(@NonNull final Context context, @NonNull final Artist artist) {
return artist.genres.size() != 0 ? artist.genres.get(0).name : "";
return !artist.genres.isEmpty() ? artist.genres.get(0).name : "";
}
@NonNull

View file

@ -12,6 +12,7 @@ import androidx.core.util.Pair;
import org.adrianvictor.geleia.activities.LoginActivity;
import org.adrianvictor.geleia.activities.MainActivity;
import org.adrianvictor.geleia.activities.SelectActivity;
import org.adrianvictor.geleia.activities.UnreachableActivity;
import org.adrianvictor.geleia.model.Album;
import org.adrianvictor.geleia.model.Artist;
import org.adrianvictor.geleia.model.Genre;
@ -61,6 +62,13 @@ public class NavigationUtil {
context.startActivity(intent);
}
public static void startUnreachable(Context context) {
final Intent intent = new Intent(context, UnreachableActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
}
public static void startSelect(Context context) {
final Intent intent = new Intent(context, SelectActivity.class);

View file

@ -48,7 +48,7 @@ public class PlaylistUtil {
PlaylistCreationRequest request = new PlaylistCreationRequest();
request.setUserId(App.getApiClient().getCurrentUserId());
request.setName(name);
if (ids.size() != 0) request.setItemIdList(ids);
if (!ids.isEmpty()) request.setItemIdList(ids);
App.getApiClient().CreatePlaylist(request, new Response<>());
}

View file

@ -448,7 +448,6 @@ public final class PreferenceUtil {
}).collect(Collectors.toList());
}
@SuppressWarnings("SimplifyStreamApiCallChains")
public void setCategories(List<Category> categories) {
List<String> values = categories.stream().map(category -> {
return category.select ? category.toString() : category.toString().toLowerCase();

View file

@ -36,7 +36,7 @@ public class DynamicShortcutManager {
}
public void initDynamicShortcuts() {
if (shortcutManager.getDynamicShortcuts().size() == 0) {
if (shortcutManager.getDynamicShortcuts().isEmpty()) {
shortcutManager.setDynamicShortcuts(getDefaultShortcuts());
}
}

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="org.adrianvictor.geleia.activities.UnreachableActivity">
<include layout="@layout/status_bar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_margin="16dp"
android:clipToPadding="false"
android:isScrollContainer="true">
<include layout="@layout/fragment_offline" />
</ScrollView>
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:layout_marginBottom="16dp"
android:onClick="onSelectClick"
android:text="@string/change_server" />
</LinearLayout>

View file

@ -0,0 +1,40 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_margin="32dp"
tools:context=".fragments.OfflineFragment">
<TextView
android:id="@+id/offline_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:text="@string/sad_face"
android:textAppearance="@style/TextAppearance.AppCompat.Display3" />
<TextView
android:id="@+id/error_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/offline_icon"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:text="@string/oops"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<TextView
android:id="@+id/error_messsage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/error_title"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:text="@string/server_is_unreachable"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</RelativeLayout>

View file

@ -236,5 +236,10 @@
<string name="dkanada_summary">Forking Phonograph and making Gelli</string>
<string name="adrianvictor">Adrian Victor</string>
<string name="error_notification_title">An error occurred in Jamfish.</string>
<string name="offline">You are offline.</string>
<string name="change_server">Change server</string>
<string name="oops">Oops! I did it again...</string>
<string name="sad_face">;(</string>
<string name="server_is_unreachable">Sorry, but we couldn\'t reach this server right now.</string>
</resources>