Browse Source

Application can now check if update available

NoodleMage 9 years ago
parent
commit
d7aef2e97a

+ 2 - 1
app/build.gradle

@@ -104,6 +104,7 @@ dependencies {
     compile 'org.jsoup:jsoup:1.8.3'
     compile 'io.reactivex:rxandroid:1.1.0'
     compile 'io.reactivex:rxjava:1.1.0'
+    compile 'com.squareup.retrofit:retrofit:1.9.0'
     compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.1'
     compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION"
     compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
@@ -131,7 +132,7 @@ dependencies {
         transitive = true
     }
 
-    //Google material icons SVG.
+    // Google material icons SVG.
     compile 'com.mikepenz:google-material-typeface:2.1.0.1.original@aar'
 
     compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') {

+ 8 - 8
app/src/main/java/eu/kanade/tachiyomi/App.java

@@ -23,6 +23,10 @@ public class App extends Application {
     AppComponent applicationComponent;
     ComponentReflectionInjector<AppComponent> componentInjector;
 
+    public static App get(Context context) {
+        return (App) context.getApplicationContext();
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -38,20 +42,16 @@ public class App extends Application {
         ACRA.init(this);
     }
 
-    public static App get(Context context) {
-        return (App) context.getApplicationContext();
-    }
-
     public AppComponent getComponent() {
         return applicationComponent;
     }
 
-    public ComponentReflectionInjector<AppComponent> getComponentReflection() {
-        return componentInjector;
-    }
-
     // Needed to replace the component with a test specific one
     public void setComponent(AppComponent applicationComponent) {
         this.applicationComponent = applicationComponent;
     }
+
+    public ComponentReflectionInjector<AppComponent> getComponentReflection() {
+        return componentInjector;
+    }
 }

+ 15 - 0
app/src/main/java/eu/kanade/tachiyomi/data/rest/GithubService.java

@@ -0,0 +1,15 @@
+package eu.kanade.tachiyomi.data.rest;
+
+import retrofit.http.GET;
+import rx.Observable;
+
+
+/**
+ * Used to connect with the Github API
+ */
+public interface GithubService {
+    String SERVICE_ENDPOINT = "https://api.github.com";
+
+    @GET("/repos/inorichi/tachiyomi/releases/latest") Observable<Release> getLatestVersion();
+
+}

+ 93 - 0
app/src/main/java/eu/kanade/tachiyomi/data/rest/Release.java

@@ -0,0 +1,93 @@
+package eu.kanade.tachiyomi.data.rest;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+/**
+ * Release object
+ * Contains information about the latest release
+ */
+public class Release {
+    /**
+     * Version name V0.0.0
+     */
+    @SerializedName("tag_name")
+    private final String version;
+
+    /** Change Log */
+    @SerializedName("body")
+    private final String log;
+
+    /** Assets containing download url */
+    @SerializedName("assets")
+    private final List<Assets> assets;
+
+    /**
+     * Release constructor
+     *
+     * @param version version of latest release
+     * @param log     log of latest release
+     * @param assets  assets of latest release
+     */
+    public Release(String version, String log, List<Assets> assets) {
+        this.version = version;
+        this.log = log;
+        this.assets = assets;
+    }
+
+    /**
+     * Get latest release version
+     *
+     * @return latest release version
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    /**
+     * Get change log of latest release
+     *
+     * @return change log of latest release
+     */
+    public String getChangeLog() {
+        return log;
+    }
+
+    /**
+     * Get download link of latest release
+     *
+     * @return download link of latest release
+     */
+    public String getDownloadLink() {
+        return assets.get(0).getDownloadLink();
+    }
+
+    /**
+     * Assets class containing download url
+     */
+    class Assets {
+        @SerializedName("browser_download_url")
+        private final String download_url;
+
+
+        /**
+         * Assets Constructor
+         *
+         * @param download_url download url
+         */
+        @SuppressWarnings("unused") public Assets(String download_url) {
+            this.download_url = download_url;
+        }
+
+        /**
+         * Get download link of latest release
+         *
+         * @return download link of latest release
+         */
+        public String getDownloadLink() {
+            return download_url;
+        }
+    }
+}
+

+ 21 - 0
app/src/main/java/eu/kanade/tachiyomi/data/rest/ServiceFactory.java

@@ -0,0 +1,21 @@
+package eu.kanade.tachiyomi.data.rest;
+
+import retrofit.RestAdapter;
+
+public class ServiceFactory {
+
+    /**
+     * Creates a retrofit service from an arbitrary class (clazz)
+     *
+     * @param clazz    Java interface of the retrofit service
+     * @param endPoint REST endpoint url
+     * @return retrofit service with defined endpoint
+     */
+    public static <T> T createRetrofitService(final Class<T> clazz, final String endPoint) {
+        final RestAdapter restAdapter = new RestAdapter.Builder()
+                .setEndpoint(endPoint)
+                .build();
+
+        return restAdapter.create(clazz);
+    }
+}

+ 31 - 0
app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.java

@@ -0,0 +1,31 @@
+package eu.kanade.tachiyomi.data.updater;
+
+
+import android.content.Context;
+
+import eu.kanade.tachiyomi.R;
+import eu.kanade.tachiyomi.data.rest.GithubService;
+import eu.kanade.tachiyomi.data.rest.Release;
+import eu.kanade.tachiyomi.data.rest.ServiceFactory;
+import eu.kanade.tachiyomi.util.ToastUtil;
+import rx.Observable;
+
+
+public class UpdateChecker {
+    private final Context context;
+
+    public UpdateChecker(Context context) {
+        this.context = context;
+    }
+
+    /**
+     * Returns observable containing release information
+     *
+     */
+    public Observable<Release> checkForApplicationUpdate() {
+        ToastUtil.showShort(context, context.getString(R.string.update_check_look_for_updates));
+        //Create Github service to retrieve Github data
+        GithubService service = ServiceFactory.createRetrofitService(GithubService.class, GithubService.SERVICE_ENDPOINT);
+        return service.getLatestVersion();
+    }
+}

+ 99 - 0
app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.java

@@ -0,0 +1,99 @@
+package eu.kanade.tachiyomi.data.updater;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import javax.inject.Inject;
+
+import eu.kanade.tachiyomi.App;
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
+
+public class UpdateDownloader extends AsyncTask<String, Void, Void> {
+    /**
+     * Name of cache directory.
+     */
+    private static final String PARAMETER_CACHE_DIRECTORY = "apk_downloads";
+    /**
+     * Interface to global information about an application environment.
+     */
+    private final Context context;
+    /**
+     * Cache directory used for cache management.
+     */
+    private final File cacheDir;
+    @Inject PreferencesHelper preferencesHelper;
+
+    /**
+     * Constructor of UpdaterCache.
+     *
+     * @param context application environment interface.
+     */
+    public UpdateDownloader(Context context) {
+        App.get(context).getComponent().inject(this);
+        this.context = context;
+
+        // Get cache directory from parameter.
+        cacheDir = new File(preferencesHelper.getDownloadsDirectory(), PARAMETER_CACHE_DIRECTORY);
+
+        // Create cache directory.
+        createCacheDir();
+    }
+
+    /**
+     * Create cache directory if it doesn't exist
+     *
+     * @return true if cache dir is created otherwise false.
+     */
+    @SuppressWarnings("UnusedReturnValue")
+    private boolean createCacheDir() {
+        return !cacheDir.exists() && cacheDir.mkdirs();
+    }
+
+
+    @Override
+    protected Void doInBackground(String... arg0) {
+        try {
+            createCacheDir();
+
+            URL url = new URL(arg0[0]);
+            HttpURLConnection c = (HttpURLConnection) url.openConnection();
+            c.connect();
+
+            File outputFile = new File(cacheDir, "update.apk");
+            if (outputFile.exists()) {
+                //noinspection ResultOfMethodCallIgnored
+                outputFile.delete();
+            }
+            FileOutputStream fos = new FileOutputStream(outputFile);
+
+            InputStream is = c.getInputStream();
+
+            byte[] buffer = new byte[1024];
+            int len1;
+            while ((len1 = is.read(buffer)) != -1) {
+                fos.write(buffer, 0, len1);
+            }
+            fos.close();
+            is.close();
+
+            // Prompt install interface
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            intent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive");
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
+            context.startActivity(intent);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
+

+ 4 - 0
app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java

@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.mangasync.services.MyAnimeList;
 import eu.kanade.tachiyomi.data.source.base.Source;
 import eu.kanade.tachiyomi.data.sync.LibraryUpdateService;
 import eu.kanade.tachiyomi.data.sync.UpdateMangaSyncService;
+import eu.kanade.tachiyomi.data.updater.UpdateDownloader;
 import eu.kanade.tachiyomi.injection.module.AppModule;
 import eu.kanade.tachiyomi.injection.module.DataModule;
 import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter;
@@ -50,6 +51,7 @@ public interface AppComponent {
     void inject(ReaderActivity readerActivity);
     void inject(MangaActivity mangaActivity);
     void inject(SettingsAccountsFragment settingsAccountsFragment);
+
     void inject(SettingsActivity settingsActivity);
 
     void inject(Source source);
@@ -59,6 +61,8 @@ public interface AppComponent {
     void inject(LibraryUpdateService libraryUpdateService);
     void inject(DownloadService downloadService);
     void inject(UpdateMangaSyncService updateMangaSyncService);
+
+    void inject(UpdateDownloader updateDownloader);
     Application application();
 
 }

+ 77 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.java

@@ -6,6 +6,8 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.afollestad.materialdialogs.MaterialDialog;
+
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -15,8 +17,23 @@ import java.util.TimeZone;
 
 import eu.kanade.tachiyomi.BuildConfig;
 import eu.kanade.tachiyomi.R;
+import eu.kanade.tachiyomi.data.updater.UpdateChecker;
+import eu.kanade.tachiyomi.data.updater.UpdateDownloader;
+import eu.kanade.tachiyomi.util.ToastUtil;
+import rx.Subscription;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
 
 public class SettingsAboutFragment extends SettingsNestedFragment {
+    /**
+     * Checks for new releases
+     */
+    private UpdateChecker updateChecker;
+
+    /**
+     * The subscribtion service of the obtained release object
+     */
+    private Subscription releaseSubscription;
 
     public static SettingsNestedFragment newInstance(int resourcePreference, int resourceTitle) {
         SettingsNestedFragment fragment = new SettingsAboutFragment();
@@ -25,14 +42,36 @@ public class SettingsAboutFragment extends SettingsNestedFragment {
     }
 
     @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+    public void onCreate(Bundle savedInstanceState) {
+        //Check for update
+        updateChecker = new UpdateChecker(getActivity());
+
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onDestroyView() {
+        if (releaseSubscription != null)
+            releaseSubscription.unsubscribe();
+
+        super.onDestroyView();
+    }
 
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
         Preference version = findPreference(getString(R.string.pref_version));
         Preference buildTime = findPreference(getString(R.string.pref_build_time));
 
         version.setSummary(BuildConfig.DEBUG ? "r" + BuildConfig.COMMIT_COUNT :
                 BuildConfig.VERSION_NAME);
 
+        //Set onClickListener to check for new version
+        version.setOnPreferenceClickListener(preference -> {
+            if (!BuildConfig.DEBUG)
+                checkVersion();
+            return true;
+        });
+
         buildTime.setSummary(getFormattedBuildTime());
 
         return super.onCreateView(inflater, container, savedState);
@@ -54,4 +93,41 @@ public class SettingsAboutFragment extends SettingsNestedFragment {
         }
         return "";
     }
+
+    /**
+     * Checks version and shows a user prompt when update available.
+     */
+    private void checkVersion() {
+        releaseSubscription = updateChecker.checkForApplicationUpdate()
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(next -> {
+                            //Get version of latest releaseSubscription
+                            String newVersion = next.getVersion();
+                            newVersion = newVersion.replaceAll("[^\\d.]", "");
+
+                            //Check if latest version is different from current version
+                            if (!newVersion.equals(BuildConfig.VERSION_NAME)) {
+                                String downloadLink = next.getDownloadLink();
+                                String body = next.getChangeLog();
+
+                                //Create confirmation window
+                                new MaterialDialog.Builder(getActivity())
+                                        .title(getString(R.string.update_check_title))
+                                        .content(body)
+                                        .positiveText(getString(R.string.update_check_confirm))
+                                        .negativeText(getString(R.string.update_check_ignore))
+                                        .onPositive((dialog, which) -> {
+                                            // User output that download has started
+                                            ToastUtil.showShort(getActivity(), getString(R.string.update_check_download_started));
+                                            // Start download
+                                            new UpdateDownloader(getActivity()).execute(downloadLink);
+                                        })
+                                        .show();
+                            } else {
+                                ToastUtil.showShort(getActivity(), getString(R.string.update_check_no_new_updates));
+                            }
+                        },
+                        Throwable::printStackTrace);
+    }
 }

+ 8 - 0
app/src/main/res/values/strings.xml

@@ -210,4 +210,12 @@
     <!-- File Picker Titles -->
     <string name="file_select_cover">Select cover image</string>
 
+    <!--UpdateCheck-->
+    <string name="update_check_title">New update available!</string>
+    <string name="update_check_confirm">Download</string>
+    <string name="update_check_ignore">Ignore</string>
+    <string name="update_check_no_new_updates">No new updates available</string>
+    <string name="update_check_download_started">Download Started</string>
+    <string name="update_check_look_for_updates">Looking for updates</string>
+
 </resources>