Skip to content

Commit 3908389

Browse files
committed
added app update notifier back
1 parent 0ad1a42 commit 3908389

File tree

7 files changed

+206
-1
lines changed

7 files changed

+206
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@
5757
- [x] reader text formatting with font size and bg/text color selection
5858
- [x] continuous vertical scroll for reader
5959
- [x] add error text/art to denote no results found in search
60-
- [x] update app when a new source is selected
6160
- [x] add settings page with theme selection
61+
- [x] new app update notifier
6262
- [x] sources list selection
6363
- [x] add personal source
6464
- [x] remembering source selection (only one source at a time, that's the rule)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package org.ranobe.ranobe.network.repository;
2+
3+
import org.json.JSONObject;
4+
import org.ranobe.ranobe.BuildConfig;
5+
import org.ranobe.ranobe.network.HttpClient;
6+
import org.ranobe.ranobe.util.Version;
7+
8+
import java.util.HashMap;
9+
import java.util.concurrent.Executor;
10+
import java.util.concurrent.Executors;
11+
12+
public class GithubRepo {
13+
private static final String API_ENDPOINT = "https://api.github.com/repos/ranobe-org/ranobe/releases/latest";
14+
private final HashMap<String, String> HEADERS = new HashMap<String, String>() {{
15+
put("Accept", "application/vnd.github+json");
16+
}};
17+
private final Executor executor;
18+
19+
public GithubRepo(){
20+
this.executor = Executors.newCachedThreadPool();
21+
}
22+
23+
public interface Callback<T> {
24+
void onComplete(T result);
25+
26+
void onError(Exception e);
27+
}
28+
29+
public void getLatestRelease(Callback<GithubRelease> callback) {
30+
this.executor.execute(() -> {
31+
try {
32+
GithubRelease release = fetchLatestRelease();
33+
callback.onComplete(release);
34+
} catch (Exception e) {
35+
callback.onError(e);
36+
}
37+
});
38+
}
39+
40+
private GithubRelease fetchLatestRelease() throws Exception {
41+
String json = HttpClient.GET(API_ENDPOINT, HEADERS);
42+
JSONObject response = new JSONObject(json);
43+
44+
String tag = response.getString("tag_name");
45+
Version latestVersion = new Version(Version.extractVersionNumber(tag));
46+
Version currentVersion = new Version(Version.extractVersionNumber(BuildConfig.VERSION_NAME));
47+
48+
if(latestVersion.get().equals(currentVersion.get())) {
49+
return new GithubRelease(false, currentVersion.get(), null);
50+
}
51+
52+
if(latestVersion.compareTo(currentVersion) > 0) {
53+
String url = response.getString("html_url");
54+
return new GithubRelease(true, tag, url);
55+
}
56+
57+
return new GithubRelease(false, currentVersion.get(), null);
58+
}
59+
60+
public static class GithubRelease {
61+
public boolean updateAvailable;
62+
public String newReleaseVersion;
63+
public String newReleaseUrl;
64+
65+
public GithubRelease(boolean updateAvailable, String newReleaseVersion, String newReleaseUrl) {
66+
this.updateAvailable = updateAvailable;
67+
this.newReleaseVersion = newReleaseVersion;
68+
this.newReleaseUrl = newReleaseUrl;
69+
}
70+
}
71+
}

app/src/main/java/org/ranobe/ranobe/ui/settings/Settings.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,28 @@
1010
import androidx.annotation.NonNull;
1111
import androidx.appcompat.app.AppCompatDelegate;
1212
import androidx.fragment.app.Fragment;
13+
import androidx.lifecycle.ViewModelProvider;
1314

1415
import org.ranobe.ranobe.R;
1516
import org.ranobe.ranobe.config.Ranobe;
1617
import org.ranobe.ranobe.databinding.FragmentSettingsBinding;
18+
import org.ranobe.ranobe.network.repository.GithubRepo;
19+
import org.ranobe.ranobe.ui.settings.viewmodel.SettingsViewModel;
1720

1821
public class Settings extends Fragment {
1922

2023
private FragmentSettingsBinding binding;
24+
private SettingsViewModel viewModel;
2125

2226
public Settings() {
2327
}
2428

29+
@Override
30+
public void onCreate(Bundle savedInstanceState) {
31+
super.onCreate(savedInstanceState);
32+
viewModel = new ViewModelProvider(requireActivity()).get(SettingsViewModel.class);
33+
}
34+
2535
@Override
2636
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
2737
Bundle savedInstanceState) {
@@ -38,9 +48,20 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
3848

3949
setCurrentThemeMode();
4050

51+
viewModel.getUpdate().observe(requireActivity(), this::release);
52+
viewModel.checkForUpdate();
53+
4154
return binding.getRoot();
4255
}
4356

57+
private void release(GithubRepo.GithubRelease release) {
58+
if (release.updateAvailable) {
59+
binding.updateCard.setVisibility(View.VISIBLE);
60+
binding.versionString.setText(release.newReleaseVersion);
61+
binding.getUpdate.setOnClickListener(v -> openLink(release.newReleaseUrl));
62+
}
63+
}
64+
4465
private void setCurrentThemeMode() {
4566
int mode = Ranobe.getThemeMode(requireActivity().getApplicationContext());
4667
if (mode == AppCompatDelegate.MODE_NIGHT_NO)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.ranobe.ranobe.ui.settings.viewmodel;
2+
import androidx.lifecycle.MutableLiveData;
3+
import androidx.lifecycle.ViewModel;
4+
5+
import org.ranobe.ranobe.network.repository.GithubRepo;
6+
7+
public class SettingsViewModel extends ViewModel {
8+
private MutableLiveData<GithubRepo.GithubRelease> update;
9+
10+
public MutableLiveData<GithubRepo.GithubRelease> getUpdate() {
11+
if (update == null) {
12+
update = new MutableLiveData<>();
13+
}
14+
return update;
15+
}
16+
17+
public void checkForUpdate() {
18+
new GithubRepo().getLatestRelease(new GithubRepo.Callback<GithubRepo.GithubRelease>() {
19+
@Override
20+
public void onComplete(GithubRepo.GithubRelease result) {
21+
update.postValue(result);
22+
}
23+
24+
@Override
25+
public void onError(Exception e) {
26+
e.printStackTrace();
27+
}
28+
});
29+
}
30+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.ranobe.ranobe.util;
2+
3+
import java.util.regex.Matcher;
4+
import java.util.regex.Pattern;
5+
6+
public class Version implements Comparable<Version> {
7+
private final String version;
8+
9+
public final String get() {
10+
return this.version;
11+
}
12+
13+
public Version(String version) {
14+
if(version == null)
15+
throw new IllegalArgumentException("Version can not be null");
16+
if(!version.matches("[0-9]+(\\.[0-9]+)*"))
17+
throw new IllegalArgumentException("Invalid version format");
18+
this.version = version;
19+
}
20+
21+
public static String extractVersionNumber(String input) {
22+
Pattern regex = Pattern.compile("\\d+\\.\\d+\\.\\d+", Pattern.MULTILINE); // x.x.x
23+
Matcher match = regex.matcher(input);
24+
return match.find() ? match.group() : null;
25+
}
26+
27+
@Override public int compareTo(Version that) {
28+
if(that == null)
29+
return 1;
30+
String[] thisParts = this.get().split("\\.");
31+
String[] thatParts = that.get().split("\\.");
32+
int length = Math.max(thisParts.length, thatParts.length);
33+
for(int i = 0; i < length; i++) {
34+
int thisPart = i < thisParts.length ?
35+
Integer.parseInt(thisParts[i]) : 0;
36+
int thatPart = i < thatParts.length ?
37+
Integer.parseInt(thatParts[i]) : 0;
38+
if(thisPart < thatPart)
39+
return -1;
40+
if(thisPart > thatPart)
41+
return 1;
42+
}
43+
return 0;
44+
}
45+
46+
}

app/src/main/res/layout/fragment_settings.xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,41 @@
1212
android:orientation="vertical"
1313
android:padding="10dp">
1414

15+
<com.google.android.material.card.MaterialCardView
16+
android:id="@+id/update_card"
17+
android:layout_width="match_parent"
18+
android:layout_height="wrap_content"
19+
android:visibility="gone"
20+
android:layout_marginBottom="10dp">
21+
<LinearLayout
22+
android:layout_width="match_parent"
23+
android:layout_height="wrap_content"
24+
android:orientation="vertical"
25+
android:padding="15dp">
26+
27+
<TextView
28+
android:layout_width="match_parent"
29+
android:layout_height="wrap_content"
30+
android:text="@string/new_version_available"
31+
android:textColor="?colorOnSurface"
32+
android:textSize="@dimen/text_med"/>
33+
34+
<TextView
35+
android:id="@+id/version_string"
36+
android:layout_width="match_parent"
37+
android:layout_height="wrap_content"/>
38+
39+
<com.google.android.material.button.MaterialButton
40+
android:id="@+id/get_update"
41+
android:layout_width="wrap_content"
42+
android:layout_height="wrap_content"
43+
android:text="@string/update"
44+
android:layout_gravity="end"/>
45+
46+
</LinearLayout>
47+
48+
</com.google.android.material.card.MaterialCardView>
49+
1550
<TextView
1651
android:layout_width="match_parent"
1752
android:layout_height="wrap_content"

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@
2525
<string name="mp_lite_subtitle">an open source music player from same dev</string>
2626
<string name="add_to_lib_info">Add to library</string>
2727
<string name="no_novels_error">There are no novels in your library. Go to Explore and find some!</string>
28+
<string name="new_version_available">A new version of the app is available</string>
29+
<string name="update">Update</string>
2830

2931
</resources>

0 commit comments

Comments
 (0)