diff --git a/api/build.gradle b/api/build.gradle index c1ab0760..4fa10611 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -49,6 +49,7 @@ dependencies { androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.9.0' + testImplementation "org.koin:koin-test:2.1.6" implementation 'com.gitlab.mvysny.konsume-xml:konsume-xml:0.12' diff --git a/api/src/androidTest/java/com/readrops/api/localfeed/LocalRSSDataSourceTest.kt b/api/src/androidTest/java/com/readrops/api/localfeed/LocalRSSDataSourceTest.kt index a03f6032..22b87e68 100644 --- a/api/src/androidTest/java/com/readrops/api/localfeed/LocalRSSDataSourceTest.kt +++ b/api/src/androidTest/java/com/readrops/api/localfeed/LocalRSSDataSourceTest.kt @@ -4,13 +4,13 @@ import android.accounts.NetworkErrorException import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import com.readrops.api.utils.HttpManager import com.readrops.api.utils.LibUtils import com.readrops.api.utils.ParseException import com.readrops.api.utils.UnknownFormatException import junit.framework.TestCase.* import okhttp3.Headers import okhttp3.HttpUrl +import okhttp3.OkHttpClient import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okio.Buffer @@ -28,7 +28,7 @@ class LocalRSSDataSourceTest { private lateinit var url: HttpUrl private val mockServer: MockWebServer = MockWebServer() - private val localRSSDataSource = LocalRSSDataSource(HttpManager.getInstance().okHttpClient) + private val localRSSDataSource = LocalRSSDataSource(OkHttpClient()) @Before fun before() { diff --git a/api/src/androidTest/java/com/readrops/api/utils/AuthInterceptorTest.kt b/api/src/androidTest/java/com/readrops/api/utils/AuthInterceptorTest.kt new file mode 100644 index 00000000..ea425a05 --- /dev/null +++ b/api/src/androidTest/java/com/readrops/api/utils/AuthInterceptorTest.kt @@ -0,0 +1,54 @@ +package com.readrops.api.utils + +import com.readrops.api.services.freshrss.FreshRSSCredentials +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.After +import org.junit.Before +import org.junit.Test + +class AuthInterceptorTest { + + private val interceptor = AuthInterceptor() + private val mockServer = MockWebServer() + private lateinit var okHttpClient: OkHttpClient + + @Before + fun before() { + okHttpClient = OkHttpClient.Builder().addInterceptor(interceptor).build() + mockServer.start(8080) + } + + @After + fun tearDown() { + mockServer.close() + } + + @Test + fun credentialsUrlTest() { + mockServer.enqueue(MockResponse()) + interceptor.credentials = FreshRSSCredentials("token", "http://localhost:8080/rss") + + okHttpClient.newCall(Request.Builder().url(mockServer.url("/url")).build()).execute() + val request = mockServer.takeRequest() + + assertEquals(request.requestUrl.toString(), "http://localhost:8080/rss/url") + assertEquals(request.headers["Authorization"], "GoogleLogin auth=token") + } + + @Test + fun nullCredentialsTest() { + mockServer.enqueue(MockResponse()) + interceptor.credentials = null + + okHttpClient.newCall(Request.Builder().url(mockServer.url("/url")).build()).execute() + val request = mockServer.takeRequest() + + assertEquals(request.requestUrl.toString(), "http://localhost:8080/url") + assertNull(request.headers["Authorization"]) + } +} \ No newline at end of file diff --git a/api/src/main/java/com/readrops/api/ApiModule.kt b/api/src/main/java/com/readrops/api/ApiModule.kt new file mode 100644 index 00000000..81d9b800 --- /dev/null +++ b/api/src/main/java/com/readrops/api/ApiModule.kt @@ -0,0 +1,102 @@ +package com.readrops.api + +import com.readrops.api.localfeed.LocalRSSDataSource +import com.readrops.api.services.freshrss.FreshRSSDataSource +import com.readrops.api.services.freshrss.FreshRSSService +import com.readrops.api.services.freshrss.adapters.FreshRSSFeedsAdapter +import com.readrops.api.services.freshrss.adapters.FreshRSSFoldersAdapter +import com.readrops.api.services.freshrss.adapters.FreshRSSItemsAdapter +import com.readrops.api.services.nextcloudnews.NextNewsDataSource +import com.readrops.api.services.nextcloudnews.NextNewsService +import com.readrops.api.services.nextcloudnews.adapters.NextNewsFeedsAdapter +import com.readrops.api.services.nextcloudnews.adapters.NextNewsFoldersAdapter +import com.readrops.api.services.nextcloudnews.adapters.NextNewsItemsAdapter +import com.readrops.api.utils.AuthInterceptor +import com.readrops.db.entities.Item +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import okhttp3.OkHttpClient +import org.koin.core.qualifier.named +import org.koin.dsl.module +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.moshi.MoshiConverterFactory +import java.util.concurrent.TimeUnit + +val apiModule = module { + + single(createdAtStart = true) { + OkHttpClient.Builder() + .callTimeout(1, TimeUnit.MINUTES) + .readTimeout(1, TimeUnit.HOURS) + .addInterceptor(get()) + .build() + } + + single { LocalRSSDataSource(get()) } + + //region freshrss + + single { + FreshRSSDataSource(get()) + } + + single { + get(named("freshrssRetrofit")) + .create(FreshRSSService::class.java) + } + + single(named("freshrssRetrofit")) { + get() + .addConverterFactory(MoshiConverterFactory.create(get(named("freshrssMoshi")))) + .build() + } + + single(named("freshrssMoshi")) { + Moshi.Builder() + .add(Types.newParameterizedType(List::class.java, Item::class.java), FreshRSSItemsAdapter()) + .add(FreshRSSFeedsAdapter()) + .add(FreshRSSFoldersAdapter()) + .build() + } + + //endregion freshrss + + //region nextcloud news + + single { + NextNewsDataSource(get()) + } + + single { + get(named("nextcloudNewsRetrofit")) + .create(NextNewsService::class.java) + } + + single(named("nextcloudNewsRetrofit")) { + get() + .addConverterFactory(MoshiConverterFactory.create(get(named("nextcloudNewsMoshi")))) + .build() + } + + single(named("nextcloudNewsMoshi")) { + Moshi.Builder() + .add(NextNewsFeedsAdapter()) + .add(NextNewsFoldersAdapter()) + .add(Types.newParameterizedType(List::class.java, Item::class.java), NextNewsItemsAdapter()) + .build() + } + + //endregion nextcloud news + + single { + Retrofit.Builder() // url will be set dynamically in an interceptor + .baseUrl("https://baseurl.com") + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .client(get()) + } + + single { + AuthInterceptor() + } +} \ No newline at end of file diff --git a/api/src/main/java/com/readrops/api/services/API.java b/api/src/main/java/com/readrops/api/services/API.java deleted file mode 100644 index d86e938a..00000000 --- a/api/src/main/java/com/readrops/api/services/API.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.readrops.api.services; - -import androidx.annotation.NonNull; - -import com.readrops.api.utils.HttpManager; -import com.squareup.moshi.Moshi; - -import retrofit2.Retrofit; -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import retrofit2.converter.moshi.MoshiConverterFactory; - -/** - * Abstraction level for services APIs - * - * @param an API service interface - */ -public abstract class API { - - protected static final int MAX_ITEMS = 5000; - - protected T api; - private Retrofit retrofit; - - private Class clazz; - private String endPoint; - - public API(Credentials credentials, @NonNull Class clazz, @NonNull String endPoint) { - this.clazz = clazz; - this.endPoint = endPoint; - - api = createAPI(credentials); - } - - protected abstract Moshi buildMoshi(); - - protected Retrofit getConfiguredRetrofitInstance() { - return new Retrofit.Builder() - .baseUrl(HttpManager.getInstance().getCredentials().getUrl() + endPoint) - .addConverterFactory(MoshiConverterFactory.create(buildMoshi())) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .client(HttpManager.getInstance().getOkHttpClient()) - .build(); - } - - private T createAPI(@NonNull Credentials credentials) { - HttpManager.getInstance().setCredentials(credentials); - retrofit = getConfiguredRetrofitInstance(); - - return retrofit.create(clazz); - } - - public void setCredentials(@NonNull Credentials credentials) { - HttpManager.getInstance().setCredentials(credentials); - - retrofit = retrofit.newBuilder() - .baseUrl(credentials.getUrl() + endPoint) - .build(); - - api = retrofit.create(clazz); - } -} diff --git a/api/src/main/java/com/readrops/api/services/Credentials.java b/api/src/main/java/com/readrops/api/services/Credentials.java index 53fee513..b77df749 100644 --- a/api/src/main/java/com/readrops/api/services/Credentials.java +++ b/api/src/main/java/com/readrops/api/services/Credentials.java @@ -1,16 +1,17 @@ package com.readrops.api.services; -import androidx.annotation.Nullable; - -import com.readrops.db.entities.account.Account; import com.readrops.api.services.freshrss.FreshRSSCredentials; +import com.readrops.api.services.freshrss.FreshRSSService; import com.readrops.api.services.nextcloudnews.NextNewsCredentials; +import com.readrops.api.services.nextcloudnews.NextNewsService; +import com.readrops.db.entities.account.Account; +import com.readrops.db.entities.account.AccountType; public abstract class Credentials { - private String authorization; + private final String authorization; - private String url; + private final String url; public Credentials(String authorization, String url) { this.authorization = authorization; @@ -25,15 +26,27 @@ public abstract class Credentials { return url; } - @Nullable public static Credentials toCredentials(Account account) { + String endPoint = getEndPoint(account.getAccountType()); + switch (account.getAccountType()) { case NEXTCLOUD_NEWS: - return new NextNewsCredentials(account.getLogin(), account.getPassword(), account.getUrl()); + return new NextNewsCredentials(account.getLogin(), account.getPassword(), account.getUrl() + endPoint); case FRESHRSS: - return new FreshRSSCredentials(account.getToken(), account.getUrl()); + return new FreshRSSCredentials(account.getToken(), account.getUrl() + endPoint); default: - return null; + throw new IllegalArgumentException("Unknown account type"); + } + } + + private static String getEndPoint(AccountType accountType) { + switch (accountType) { + case FRESHRSS: + return FreshRSSService.END_POINT; + case NEXTCLOUD_NEWS: + return NextNewsService.END_POINT; + default: + throw new IllegalArgumentException("Unknown account type"); } } } diff --git a/api/src/main/java/com/readrops/api/services/freshrss/FreshRSSAPI.java b/api/src/main/java/com/readrops/api/services/freshrss/FreshRSSDataSource.java similarity index 90% rename from api/src/main/java/com/readrops/api/services/freshrss/FreshRSSAPI.java rename to api/src/main/java/com/readrops/api/services/freshrss/FreshRSSDataSource.java index 86fccf7d..ac4a8b56 100644 --- a/api/src/main/java/com/readrops/api/services/freshrss/FreshRSSAPI.java +++ b/api/src/main/java/com/readrops/api/services/freshrss/FreshRSSDataSource.java @@ -3,19 +3,12 @@ package com.readrops.api.services.freshrss; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.readrops.api.services.SyncResult; +import com.readrops.api.services.SyncType; +import com.readrops.api.services.freshrss.json.FreshRSSUserInfo; import com.readrops.db.entities.Feed; import com.readrops.db.entities.Folder; import com.readrops.db.entities.Item; -import com.readrops.api.services.API; -import com.readrops.api.services.Credentials; -import com.readrops.api.services.SyncResult; -import com.readrops.api.services.SyncType; -import com.readrops.api.services.freshrss.adapters.FreshRSSFeedsAdapter; -import com.readrops.api.services.freshrss.adapters.FreshRSSFoldersAdapter; -import com.readrops.api.services.freshrss.adapters.FreshRSSItemsAdapter; -import com.readrops.api.services.freshrss.json.FreshRSSUserInfo; -import com.squareup.moshi.Moshi; -import com.squareup.moshi.Types; import java.io.StringReader; import java.util.List; @@ -26,23 +19,18 @@ import io.reactivex.Single; import okhttp3.MultipartBody; import okhttp3.RequestBody; -public class FreshRSSAPI extends API { +public class FreshRSSDataSource { + + private static final int MAX_ITEMS = 5000; public static final String GOOGLE_READ = "user/-/state/com.google/read"; private static final String FEED_PREFIX = "feed/"; - public FreshRSSAPI(Credentials credentials) { - super(credentials, FreshRSSService.class, FreshRSSService.END_POINT); - } + private FreshRSSService api; - @Override - protected Moshi buildMoshi() { - return new Moshi.Builder() - .add(Types.newParameterizedType(List.class, Item.class), new FreshRSSItemsAdapter()) - .add(new FreshRSSFeedsAdapter()) - .add(new FreshRSSFoldersAdapter()) - .build(); + public FreshRSSDataSource(FreshRSSService api) { + this.api = api; } /** diff --git a/api/src/main/java/com/readrops/api/services/freshrss/adapters/FreshRSSItemsAdapter.kt b/api/src/main/java/com/readrops/api/services/freshrss/adapters/FreshRSSItemsAdapter.kt index 8667fa51..f6ef5f95 100644 --- a/api/src/main/java/com/readrops/api/services/freshrss/adapters/FreshRSSItemsAdapter.kt +++ b/api/src/main/java/com/readrops/api/services/freshrss/adapters/FreshRSSItemsAdapter.kt @@ -2,7 +2,7 @@ package com.readrops.api.services.freshrss.adapters import android.util.TimingLogger import com.readrops.db.entities.Item -import com.readrops.api.services.freshrss.FreshRSSAPI.GOOGLE_READ +import com.readrops.api.services.freshrss.FreshRSSDataSource.GOOGLE_READ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter diff --git a/api/src/main/java/com/readrops/api/services/nextcloudnews/NextNewsCredentials.java b/api/src/main/java/com/readrops/api/services/nextcloudnews/NextNewsCredentials.java index 40644621..24c44ab7 100644 --- a/api/src/main/java/com/readrops/api/services/nextcloudnews/NextNewsCredentials.java +++ b/api/src/main/java/com/readrops/api/services/nextcloudnews/NextNewsCredentials.java @@ -5,6 +5,6 @@ import com.readrops.api.services.Credentials; public class NextNewsCredentials extends Credentials { public NextNewsCredentials(String login, String password, String url) { - super(okhttp3.Credentials.basic(login, password), url); + super(login != null && password != null ? okhttp3.Credentials.basic(login, password) : null, url); } } diff --git a/api/src/main/java/com/readrops/api/services/nextcloudnews/NextNewsAPI.java b/api/src/main/java/com/readrops/api/services/nextcloudnews/NextNewsDataSource.java similarity index 89% rename from api/src/main/java/com/readrops/api/services/nextcloudnews/NextNewsAPI.java rename to api/src/main/java/com/readrops/api/services/nextcloudnews/NextNewsDataSource.java index eb9d8d16..ed06044d 100644 --- a/api/src/main/java/com/readrops/api/services/nextcloudnews/NextNewsAPI.java +++ b/api/src/main/java/com/readrops/api/services/nextcloudnews/NextNewsDataSource.java @@ -5,22 +5,15 @@ import android.content.res.Resources; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.Item; -import com.readrops.api.services.API; -import com.readrops.api.services.Credentials; import com.readrops.api.services.SyncResult; import com.readrops.api.services.SyncType; -import com.readrops.api.services.nextcloudnews.adapters.NextNewsFeedsAdapter; -import com.readrops.api.services.nextcloudnews.adapters.NextNewsFoldersAdapter; -import com.readrops.api.services.nextcloudnews.adapters.NextNewsItemsAdapter; import com.readrops.api.services.nextcloudnews.json.NextNewsUser; import com.readrops.api.utils.ConflictException; import com.readrops.api.utils.LibUtils; import com.readrops.api.utils.UnknownFormatException; -import com.squareup.moshi.Moshi; -import com.squareup.moshi.Types; +import com.readrops.db.entities.Feed; +import com.readrops.db.entities.Folder; +import com.readrops.db.entities.Item; import java.io.IOException; import java.util.ArrayList; @@ -30,21 +23,16 @@ import java.util.Map; import retrofit2.Response; -public class NextNewsAPI extends API { +public class NextNewsDataSource { - private static final String TAG = NextNewsAPI.class.getSimpleName(); + private static final String TAG = NextNewsDataSource.class.getSimpleName(); - public NextNewsAPI(Credentials credentials) { - super(credentials, NextNewsService.class, NextNewsService.END_POINT); - } + protected static final int MAX_ITEMS = 5000; - @Override - protected Moshi buildMoshi() { - return new Moshi.Builder() - .add(new NextNewsFeedsAdapter()) - .add(new NextNewsFoldersAdapter()) - .add(Types.newParameterizedType(List.class, Item.class), new NextNewsItemsAdapter()) - .build(); + private NextNewsService api; + + public NextNewsDataSource(NextNewsService api) { + this.api = api; } @Nullable diff --git a/api/src/main/java/com/readrops/api/utils/AuthInterceptor.kt b/api/src/main/java/com/readrops/api/utils/AuthInterceptor.kt new file mode 100644 index 00000000..76828f4b --- /dev/null +++ b/api/src/main/java/com/readrops/api/utils/AuthInterceptor.kt @@ -0,0 +1,35 @@ +package com.readrops.api.utils + +import android.net.Uri +import com.readrops.api.services.Credentials +import com.readrops.api.services.freshrss.FreshRSSService +import com.readrops.api.services.nextcloudnews.NextNewsService +import com.readrops.db.entities.account.Account +import com.readrops.db.entities.account.AccountType +import okhttp3.Interceptor +import okhttp3.Response +import java.lang.IllegalArgumentException + +class AuthInterceptor(var credentials: Credentials? = null) : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val requestBuilder = chain.request().newBuilder() + val urlBuilder = chain.request().url.newBuilder() + + if (credentials != null) { + if (credentials!!.url != null) { + val uri = Uri.parse(credentials!!.url) + urlBuilder + .scheme(uri.scheme!!) + .host(uri.host!!) + .encodedPath(uri.encodedPath + chain.request().url.encodedPath) + } + + if (credentials!!.authorization != null) { + requestBuilder.addHeader("Authorization", credentials!!.authorization) + } + } + + return chain.proceed(requestBuilder.url(urlBuilder.build()).build()) + } +} \ No newline at end of file diff --git a/api/src/main/java/com/readrops/api/utils/HttpManager.java b/api/src/main/java/com/readrops/api/utils/HttpManager.java deleted file mode 100644 index 0f4605e0..00000000 --- a/api/src/main/java/com/readrops/api/utils/HttpManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.readrops.api.utils; - -import androidx.annotation.Nullable; - -import com.readrops.api.services.Credentials; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -public class HttpManager { - - private OkHttpClient okHttpClient; - private Credentials credentials; - - public HttpManager() { - buildOkHttp(); - } - - private void buildOkHttp() { - okHttpClient = new OkHttpClient.Builder() - .callTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.HOURS) - .addInterceptor(new AuthInterceptor()) - .build(); - } - - public OkHttpClient getOkHttpClient() { - return okHttpClient; - } - - public void setCredentials(@Nullable Credentials credentials) { - this.credentials = credentials; - } - - public Credentials getCredentials() { - return credentials; - } - - private static HttpManager instance; - - public static HttpManager getInstance() { - if (instance == null) { - instance = new HttpManager(); - } - - return instance; - } - - public static void setInstance(OkHttpClient client) { - instance.okHttpClient = client; - } - - public class AuthInterceptor implements Interceptor { - - public AuthInterceptor() { - // empty constructor - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request request = chain.request(); - - if (credentials != null && credentials.getAuthorization() != null) { - request = request.newBuilder() - .addHeader("Authorization", credentials.getAuthorization()) - .build(); - } - - return chain.proceed(request); - } - } -} diff --git a/app/build.gradle b/app/build.gradle index b096650f..4ece006c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,6 +76,7 @@ dependencies { implementation "androidx.work:work-runtime-ktx:2.4.0" implementation "androidx.fragment:fragment-ktx:1.2.5" implementation "androidx.browser:browser:1.2.0" + testImplementation "org.koin:koin-test:2.1.6" implementation 'com.github.bumptech.glide:glide:4.11.0' kapt 'com.github.bumptech.glide:compiler:4.11.0' diff --git a/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java b/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java index 244f9afd..673e3081 100644 --- a/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java +++ b/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java @@ -13,13 +13,10 @@ import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; import com.facebook.flipper.plugins.inspector.DescriptorMapping; import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; import com.facebook.flipper.plugins.navigation.NavigationFlipperPlugin; -import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; import com.facebook.soloader.SoLoader; import com.icapps.niddler.core.AndroidNiddler; -import com.icapps.niddler.interceptor.okhttp.NiddlerOkHttpInterceptor; -import com.readrops.api.utils.HttpManager; public class ReadropsDebugApp extends ReadropsApp implements Configuration.Provider { @@ -40,13 +37,6 @@ public class ReadropsDebugApp extends ReadropsApp implements Configuration.Provi NetworkFlipperPlugin networkPlugin = new NetworkFlipperPlugin(); client.addPlugin(networkPlugin); - HttpManager.setInstance( - HttpManager.getInstance() - .getOkHttpClient() - .newBuilder() - .addInterceptor(new FlipperOkhttpInterceptor(networkPlugin)) - .build()); - client.addPlugin(new DatabasesFlipperPlugin(this)); client.addPlugin(CrashReporterPlugin.getInstance()); client.addPlugin(NavigationFlipperPlugin.getInstance()); @@ -65,12 +55,6 @@ public class ReadropsDebugApp extends ReadropsApp implements Configuration.Provi niddler.attachToApplication(this); - HttpManager.setInstance(HttpManager.getInstance(). - getOkHttpClient(). - newBuilder(). - addInterceptor(new NiddlerOkHttpInterceptor(niddler, "default")) - .build()); - niddler.start(); } diff --git a/app/src/main/java/com/readrops/app/AppModule.kt b/app/src/main/java/com/readrops/app/AppModule.kt new file mode 100644 index 00000000..ee0b5696 --- /dev/null +++ b/app/src/main/java/com/readrops/app/AppModule.kt @@ -0,0 +1,54 @@ +package com.readrops.app + +import androidx.preference.PreferenceManager +import com.readrops.app.repositories.FreshRSSRepository +import com.readrops.app.repositories.LocalFeedRepository +import com.readrops.app.repositories.NextNewsRepository +import com.readrops.app.utils.GlideApp +import com.readrops.app.viewmodels.* +import com.readrops.db.entities.account.Account +import com.readrops.db.entities.account.AccountType +import org.koin.android.ext.koin.androidApplication +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val appModule = module { + + factory { (account: Account) -> + when (account.accountType) { + AccountType.LOCAL -> LocalFeedRepository(get(), get(), androidContext(), account) + AccountType.NEXTCLOUD_NEWS -> NextNewsRepository(get(), get(), androidContext(), account) + AccountType.FRESHRSS -> FreshRSSRepository(get(), get(), androidContext(), account) + else -> throw IllegalArgumentException("Account type not supported") + } + } + + viewModel { + MainViewModel(get()) + } + + viewModel { + AddFeedsViewModel(get(), get()) + } + + viewModel { + ItemViewModel(get()) + } + + viewModel { + ManageFeedsFoldersViewModel(get()) + } + + viewModel { + NotificationPermissionViewModel(get()) + } + + viewModel { + AccountViewModel(get()) + } + + single { GlideApp.with(androidApplication()) } + + single { PreferenceManager.getDefaultSharedPreferences(androidContext()) } +} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/ReadropsApp.java b/app/src/main/java/com/readrops/app/ReadropsApp.java deleted file mode 100644 index b2a9c58a..00000000 --- a/app/src/main/java/com/readrops/app/ReadropsApp.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.readrops.app; - -import android.annotation.SuppressLint; -import android.app.Application; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.os.Build; - -import androidx.appcompat.app.AppCompatDelegate; -import androidx.preference.PreferenceManager; - -import com.readrops.app.utils.SharedPreferencesManager; - -import io.reactivex.plugins.RxJavaPlugins; - -@SuppressLint("Registered") -public class ReadropsApp extends Application { - - public static final String FEEDS_COLORS_CHANNEL_ID = "feedsColorsChannel"; - public static final String OPML_EXPORT_CHANNEL_ID = "opmlExportChannel"; - public static final String SYNC_CHANNEL_ID = "syncChannel"; - - @Override - public void onCreate() { - super.onCreate(); - - RxJavaPlugins.setErrorHandler(e -> { - }); - - createNotificationChannels(); - - PreferenceManager.setDefaultValues(this, R.xml.preferences, false); - - if (Boolean.valueOf(SharedPreferencesManager.readString(this, SharedPreferencesManager.SharedPrefKey.DARK_THEME))) - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - else - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); - } - - private void createNotificationChannels() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel feedsColorsChannel = new NotificationChannel(FEEDS_COLORS_CHANNEL_ID, - getString(R.string.feeds_colors), NotificationManager.IMPORTANCE_DEFAULT); - feedsColorsChannel.setDescription(getString(R.string.get_feeds_colors)); - - NotificationChannel opmlExportChannel = new NotificationChannel(OPML_EXPORT_CHANNEL_ID, - getString(R.string.opml_export), NotificationManager.IMPORTANCE_DEFAULT); - opmlExportChannel.setDescription(getString(R.string.opml_export_description)); - - NotificationChannel syncChannel = new NotificationChannel(SYNC_CHANNEL_ID, - getString(R.string.auto_synchro), NotificationManager.IMPORTANCE_LOW); - syncChannel.setDescription(getString(R.string.account_synchro)); - - NotificationManager manager = getSystemService(NotificationManager.class); - - manager.createNotificationChannel(feedsColorsChannel); - manager.createNotificationChannel(opmlExportChannel); - manager.createNotificationChannel(syncChannel); - } - } -} diff --git a/app/src/main/java/com/readrops/app/ReadropsApp.kt b/app/src/main/java/com/readrops/app/ReadropsApp.kt new file mode 100644 index 00000000..559bfa33 --- /dev/null +++ b/app/src/main/java/com/readrops/app/ReadropsApp.kt @@ -0,0 +1,68 @@ +package com.readrops.app + +import android.app.Application +import android.app.NotificationChannel +import android.app.NotificationManager +import android.os.Build +import androidx.appcompat.app.AppCompatDelegate +import androidx.preference.PreferenceManager +import com.readrops.api.apiModule +import com.readrops.app.utils.SharedPreferencesManager +import com.readrops.db.dbModule +import io.reactivex.plugins.RxJavaPlugins +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin +import org.koin.core.logger.Level + +open class ReadropsApp : Application() { + + override fun onCreate() { + super.onCreate() + RxJavaPlugins.setErrorHandler { e: Throwable? -> } + + createNotificationChannels() + PreferenceManager.setDefaultValues(this, R.xml.preferences, false) + + startKoin { + androidLogger(Level.ERROR) + androidContext(this@ReadropsApp) + + modules(apiModule, dbModule, appModule) + } + + if (SharedPreferencesManager.readString(SharedPreferencesManager.SharedPrefKey.DARK_THEME).toBoolean()) + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + else + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + } + + private fun createNotificationChannels() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val feedsColorsChannel = NotificationChannel(FEEDS_COLORS_CHANNEL_ID, + getString(R.string.feeds_colors), NotificationManager.IMPORTANCE_DEFAULT) + feedsColorsChannel.description = getString(R.string.get_feeds_colors) + + val opmlExportChannel = NotificationChannel(OPML_EXPORT_CHANNEL_ID, + getString(R.string.opml_export), NotificationManager.IMPORTANCE_DEFAULT) + opmlExportChannel.description = getString(R.string.opml_export_description) + + val syncChannel = NotificationChannel(SYNC_CHANNEL_ID, + getString(R.string.auto_synchro), NotificationManager.IMPORTANCE_LOW) + syncChannel.description = getString(R.string.account_synchro) + + val manager = getSystemService(NotificationManager::class.java)!! + + manager.createNotificationChannel(feedsColorsChannel) + manager.createNotificationChannel(opmlExportChannel) + manager.createNotificationChannel(syncChannel) + } + } + + companion object { + const val FEEDS_COLORS_CHANNEL_ID = "feedsColorsChannel" + const val OPML_EXPORT_CHANNEL_ID = "opmlExportChannel" + const val SYNC_CHANNEL_ID = "syncChannel" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/activities/AccountTypeListActivity.java b/app/src/main/java/com/readrops/app/activities/AccountTypeListActivity.java index 3af60e23..1ae20d00 100644 --- a/app/src/main/java/com/readrops/app/activities/AccountTypeListActivity.java +++ b/app/src/main/java/com/readrops/app/activities/AccountTypeListActivity.java @@ -11,7 +11,6 @@ import android.widget.LinearLayout; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; @@ -25,6 +24,8 @@ import com.readrops.app.viewmodels.AccountViewModel; import com.readrops.db.entities.account.Account; import com.readrops.db.entities.account.AccountType; +import org.koin.androidx.viewmodel.compat.ViewModelCompat; + import java.util.ArrayList; import java.util.List; @@ -55,7 +56,7 @@ public class AccountTypeListActivity extends AppCompatActivity { binding = ActivityAccountTypeListBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - viewModel = new ViewModelProvider(this).get(AccountViewModel.class); + viewModel = ViewModelCompat.getViewModel(this, AccountViewModel.class); setTitle(R.string.new_account); @@ -158,7 +159,7 @@ public class AccountTypeListActivity extends AppCompatActivity { account.setId(id.intValue()); viewModel.setAccount(account); - return viewModel.parseOPMLFile(uri); + return viewModel.parseOPMLFile(uri, this); }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/com/readrops/app/activities/AddAccountActivity.java b/app/src/main/java/com/readrops/app/activities/AddAccountActivity.java index c9167725..f952c5bd 100644 --- a/app/src/main/java/com/readrops/app/activities/AddAccountActivity.java +++ b/app/src/main/java/com/readrops/app/activities/AddAccountActivity.java @@ -8,7 +8,6 @@ import android.view.MenuItem; import android.view.View; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProvider; import com.readrops.app.R; import com.readrops.app.databinding.ActivityAddAccountBinding; @@ -18,6 +17,8 @@ import com.readrops.app.viewmodels.AccountViewModel; import com.readrops.db.entities.account.Account; import com.readrops.db.entities.account.AccountType; +import org.koin.androidx.viewmodel.compat.ViewModelCompat; + import io.reactivex.Completable; import io.reactivex.CompletableObserver; import io.reactivex.SingleObserver; @@ -46,7 +47,7 @@ public class AddAccountActivity extends AppCompatActivity { binding = ActivityAddAccountBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - viewModel = new ViewModelProvider(this).get(AccountViewModel.class); + viewModel = ViewModelCompat.getViewModel(this, AccountViewModel.class); accountType = getIntent().getParcelableExtra(ACCOUNT_TYPE); @@ -58,26 +59,20 @@ public class AddAccountActivity extends AppCompatActivity { if (forwardResult || accountToEdit != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true); - try { - if (accountToEdit != null) { - viewModel.setAccountType(accountToEdit.getAccountType()); - editAccount = true; - fillFields(); - } else { - viewModel.setAccountType(accountType); + if (accountToEdit != null) { + viewModel.setAccountType(accountToEdit.getAccountType()); + editAccount = true; + fillFields(); + } else { + viewModel.setAccountType(accountType); - binding.providerImage.setImageResource(accountType.getIconRes()); - binding.providerName.setText(accountType.getName()); - binding.addAccountName.setText(accountType.getName()); - if (accountType == AccountType.FRESHRSS) { - binding.addAccountPasswordLayout.setHelperText(getString(R.string.password_helper)); - } + binding.providerImage.setImageResource(accountType.getIconRes()); + binding.providerName.setText(accountType.getName()); + binding.addAccountName.setText(accountType.getName()); + if (accountType == AccountType.FRESHRSS) { + binding.addAccountPasswordLayout.setHelperText(getString(R.string.password_helper)); } - } catch (Exception e) { - // TODO : see how to handle this exception - e.printStackTrace(); } - } public void createAccount(View view) { @@ -183,8 +178,8 @@ public class AddAccountActivity extends AppCompatActivity { } private void saveLoginPassword(Account account) { - SharedPreferencesManager.writeValue(this, account.getLoginKey(), account.getLogin()); - SharedPreferencesManager.writeValue(this, account.getPasswordKey(), account.getPassword()); + SharedPreferencesManager.writeValue(account.getLoginKey(), account.getLogin()); + SharedPreferencesManager.writeValue(account.getPasswordKey(), account.getPassword()); account.setLogin(null); account.setPassword(null); @@ -196,8 +191,8 @@ public class AddAccountActivity extends AppCompatActivity { binding.addAccountUrl.setText(accountToEdit.getUrl()); binding.addAccountName.setText(accountToEdit.getAccountName()); - binding.addAccountLogin.setText(SharedPreferencesManager.readString(this, accountToEdit.getLoginKey())); - binding.addAccountPassword.setText(SharedPreferencesManager.readString(this, accountToEdit.getPasswordKey())); + binding.addAccountLogin.setText(SharedPreferencesManager.readString(accountToEdit.getLoginKey())); + binding.addAccountPassword.setText(SharedPreferencesManager.readString(accountToEdit.getPasswordKey())); } private void updateAccount() { diff --git a/app/src/main/java/com/readrops/app/activities/AddFeedActivity.java b/app/src/main/java/com/readrops/app/activities/AddFeedActivity.java index 9e231ff6..5828ce1a 100644 --- a/app/src/main/java/com/readrops/app/activities/AddFeedActivity.java +++ b/app/src/main/java/com/readrops/app/activities/AddFeedActivity.java @@ -11,7 +11,6 @@ import android.view.View; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; @@ -33,6 +32,8 @@ import com.readrops.app.viewmodels.AddFeedsViewModel; import com.readrops.db.entities.Feed; import com.readrops.db.entities.account.Account; +import org.koin.androidx.viewmodel.compat.ViewModelCompat; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -69,7 +70,7 @@ public class AddFeedActivity extends AppCompatActivity implements View.OnClickLi binding.addFeedOk.setOnClickListener(this); binding.addFeedOk.setEnabled(false); - viewModel = new ViewModelProvider(this).get(AddFeedsViewModel.class); + viewModel = ViewModelCompat.getViewModel(this, AddFeedsViewModel.class); parseItemsAdapter = new ItemAdapter<>(); fastAdapter = FastAdapter.with(parseItemsAdapter); @@ -259,8 +260,8 @@ public class AddFeedActivity extends AppCompatActivity implements View.OnClickLi Account account = (Account) binding.addFeedAccountSpinner.getSelectedItem(); - account.setLogin(SharedPreferencesManager.readString(this, account.getLoginKey())); - account.setPassword(SharedPreferencesManager.readString(this, account.getPasswordKey())); + account.setLogin(SharedPreferencesManager.readString(account.getLoginKey())); + account.setPassword(SharedPreferencesManager.readString(account.getPasswordKey())); viewModel.addFeeds(feedsToInsert, account) .subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/com/readrops/app/activities/ItemActivity.java b/app/src/main/java/com/readrops/app/activities/ItemActivity.java index 83e56894..14a96800 100644 --- a/app/src/main/java/com/readrops/app/activities/ItemActivity.java +++ b/app/src/main/java/com/readrops/app/activities/ItemActivity.java @@ -25,16 +25,15 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.browser.customtabs.CustomTabsIntent; import androidx.core.app.ActivityCompat; import androidx.core.app.ShareCompat; -import androidx.lifecycle.ViewModelProvider; import com.afollestad.materialdialogs.MaterialDialog; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.transition.Transition; +import com.readrops.api.utils.DateUtils; import com.readrops.app.R; import com.readrops.app.databinding.ActivityItemBinding; -import com.readrops.api.utils.DateUtils; -import com.readrops.app.utils.GlideApp; +import com.readrops.app.utils.GlideRequests; import com.readrops.app.utils.PermissionManager; import com.readrops.app.utils.SharedPreferencesManager; import com.readrops.app.utils.Utils; @@ -42,6 +41,9 @@ import com.readrops.app.viewmodels.ItemViewModel; import com.readrops.db.entities.Item; import com.readrops.db.pojo.ItemWithFeed; +import org.koin.androidx.viewmodel.compat.ViewModelCompat; +import org.koin.java.KoinJavaComponent; + import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -92,7 +94,7 @@ public class ItemActivity extends AppCompatActivity { binding.appBarLayout.setExpanded(true); binding.collapsingLayout.setTitleEnabled(true); - GlideApp.with(this) + KoinJavaComponent.get(GlideRequests.class) .load(imageUrl) .diskCacheStrategy(DiskCacheStrategy.ALL) .into(binding.collapsingLayoutImage); @@ -110,7 +112,7 @@ public class ItemActivity extends AppCompatActivity { invalidateOptionsMenu(); })); - viewModel = new ViewModelProvider(this).get(ItemViewModel.class); + viewModel = ViewModelCompat.getViewModel(this, ItemViewModel.class); viewModel.getItemById(itemId).observe(this, this::bindUI); binding.activityItemFab.setOnClickListener(v -> openInNavigator()); } @@ -214,7 +216,7 @@ public class ItemActivity extends AppCompatActivity { } private void openUrl() { - int value = Integer.parseInt(SharedPreferencesManager.readString(this, + int value = Integer.parseInt(SharedPreferencesManager.readString( SharedPreferencesManager.SharedPrefKey.OPEN_ITEMS_IN)); switch (value) { case 0: @@ -243,7 +245,7 @@ public class ItemActivity extends AppCompatActivity { } private void openInCustomTab() { - boolean darkTheme = Boolean.parseBoolean(SharedPreferencesManager.readString(this, SharedPreferencesManager.SharedPrefKey.DARK_THEME)); + boolean darkTheme = Boolean.parseBoolean(SharedPreferencesManager.readString(SharedPreferencesManager.SharedPrefKey.DARK_THEME)); int color = itemWithFeed.getBgColor() != 0 ? itemWithFeed.getBgColor() : itemWithFeed.getColor(); CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder() @@ -357,7 +359,7 @@ public class ItemActivity extends AppCompatActivity { } private void shareImage(String url) { - GlideApp.with(this) + KoinJavaComponent.get(GlideRequests.class) .asBitmap() .diskCacheStrategy(DiskCacheStrategy.ALL) .load(url) @@ -365,7 +367,7 @@ public class ItemActivity extends AppCompatActivity { @Override public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition transition) { try { - Uri uri = viewModel.saveImageInCache(resource); + Uri uri = viewModel.saveImageInCache(resource, ItemActivity.this); Intent intent = ShareCompat.IntentBuilder.from(ItemActivity.this) .setType("image/png") .setStream(uri) diff --git a/app/src/main/java/com/readrops/app/activities/MainActivity.java b/app/src/main/java/com/readrops/app/activities/MainActivity.java index 0cb7faf9..e8dac12f 100644 --- a/app/src/main/java/com/readrops/app/activities/MainActivity.java +++ b/app/src/main/java/com/readrops/app/activities/MainActivity.java @@ -16,7 +16,6 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import androidx.drawerlayout.widget.DrawerLayout; -import androidx.lifecycle.ViewModelProvider; import androidx.paging.PagedList; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.ItemTouchHelper; @@ -38,7 +37,7 @@ import com.readrops.app.R; import com.readrops.app.adapters.MainItemListAdapter; import com.readrops.app.databinding.ActivityMainBinding; import com.readrops.app.utils.DrawerManager; -import com.readrops.app.utils.GlideApp; +import com.readrops.app.utils.GlideRequests; import com.readrops.app.utils.ReadropsItemTouchCallback; import com.readrops.app.utils.SharedPreferencesManager; import com.readrops.app.utils.Utils; @@ -51,6 +50,8 @@ import com.readrops.db.filters.ListSortType; import com.readrops.db.pojo.ItemWithFeed; import org.jetbrains.annotations.NotNull; +import org.koin.androidx.viewmodel.compat.ViewModelCompat; +import org.koin.java.KoinJavaComponent; import java.lang.ref.WeakReference; import java.util.Collections; @@ -116,9 +117,9 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou feedCount = 0; initRecyclerView(); - viewModel = new ViewModelProvider(this).get(MainViewModel.class); + viewModel = ViewModelCompat.getViewModel(this, MainViewModel.class); - viewModel.setShowReadItems(SharedPreferencesManager.readBoolean(this, + viewModel.setShowReadItems(SharedPreferencesManager.readBoolean( SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES)); viewModel.getItemsWithFeed().observe(this, itemWithFeeds -> { @@ -300,7 +301,7 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou private void initRecyclerView() { ViewPreloadSizeProvider preloadSizeProvider = new ViewPreloadSizeProvider(); - adapter = new MainItemListAdapter(GlideApp.with(this), preloadSizeProvider); + adapter = new MainItemListAdapter(KoinJavaComponent.get(GlideRequests.class), preloadSizeProvider); adapter.setOnItemClickListener(new MainItemListAdapter.OnItemClickListener() { @Override public void onItemClick(ItemWithFeed itemWithFeed, int position) { @@ -349,7 +350,7 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou binding.itemsRecyclerView.setRecyclerListener(viewHolder -> { MainItemListAdapter.ItemViewHolder vh = (MainItemListAdapter.ItemViewHolder) viewHolder; - GlideApp.with(this).clear(vh.getItemImage()); + KoinJavaComponent.get(GlideRequests.class).clear(vh.getItemImage()); }); LinearLayoutManager layoutManager = new LinearLayoutManager(this); @@ -659,12 +660,12 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou if (item.isChecked()) { item.setChecked(false); viewModel.setShowReadItems(false); - SharedPreferencesManager.writeValue(this, + SharedPreferencesManager.writeValue( SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES, false); } else { item.setChecked(true); viewModel.setShowReadItems(true); - SharedPreferencesManager.writeValue(this, + SharedPreferencesManager.writeValue( SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES, true); } @@ -708,16 +709,16 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou private void getAccountCredentials(List accounts) { for (Account account : accounts) { if (account.getLogin() == null) - account.setLogin(SharedPreferencesManager.readString(this, account.getLoginKey())); + account.setLogin(SharedPreferencesManager.readString(account.getLoginKey())); if (account.getPassword() == null) - account.setPassword(SharedPreferencesManager.readString(this, account.getPasswordKey())); + account.setPassword(SharedPreferencesManager.readString(account.getPasswordKey())); } } private void startAboutActivity() { Libs.ActivityStyle activityStyle; - if (Boolean.valueOf(SharedPreferencesManager.readString(this, SharedPreferencesManager.SharedPrefKey.DARK_THEME))) + if (Boolean.valueOf(SharedPreferencesManager.readString(SharedPreferencesManager.SharedPrefKey.DARK_THEME))) activityStyle = Libs.ActivityStyle.DARK; else activityStyle = Libs.ActivityStyle.LIGHT_DARK_TOOLBAR; diff --git a/app/src/main/java/com/readrops/app/activities/ManageFeedsFoldersActivity.java b/app/src/main/java/com/readrops/app/activities/ManageFeedsFoldersActivity.java index 4b833b78..9404647a 100644 --- a/app/src/main/java/com/readrops/app/activities/ManageFeedsFoldersActivity.java +++ b/app/src/main/java/com/readrops/app/activities/ManageFeedsFoldersActivity.java @@ -9,9 +9,10 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; -import androidx.lifecycle.ViewModelProvider; import com.afollestad.materialdialogs.MaterialDialog; +import com.readrops.api.utils.ConflictException; +import com.readrops.api.utils.UnknownFormatException; import com.readrops.app.R; import com.readrops.app.databinding.ActivityManageFeedsFoldersBinding; import com.readrops.app.fragments.FeedsFragment; @@ -20,8 +21,8 @@ import com.readrops.app.utils.Utils; import com.readrops.app.viewmodels.ManageFeedsFoldersViewModel; import com.readrops.db.entities.Folder; import com.readrops.db.entities.account.Account; -import com.readrops.api.utils.ConflictException; -import com.readrops.api.utils.UnknownFormatException; + +import org.koin.androidx.viewmodel.compat.ViewModelCompat; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -52,7 +53,7 @@ public class ManageFeedsFoldersActivity extends AppCompatActivity { binding.manageFeedsFoldersViewpager.setAdapter(pageAdapter); binding.manageFeedsFoldersTablayout.setupWithViewPager(binding.manageFeedsFoldersViewpager); - viewModel = new ViewModelProvider(this).get(ManageFeedsFoldersViewModel.class); + viewModel = ViewModelCompat.getViewModel(this, ManageFeedsFoldersViewModel.class); viewModel.setAccount(account); } diff --git a/app/src/main/java/com/readrops/app/activities/NotificationPermissionActivity.kt b/app/src/main/java/com/readrops/app/activities/NotificationPermissionActivity.kt index 511de722..a0b2ff07 100644 --- a/app/src/main/java/com/readrops/app/activities/NotificationPermissionActivity.kt +++ b/app/src/main/java/com/readrops/app/activities/NotificationPermissionActivity.kt @@ -3,7 +3,6 @@ package com.readrops.app.activities import android.content.Intent import android.os.Bundle import android.view.MenuItem -import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager @@ -20,11 +19,12 @@ import com.readrops.db.entities.Feed import com.readrops.db.entities.account.Account import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers +import org.koin.androidx.viewmodel.ext.android.getViewModel class NotificationPermissionActivity : AppCompatActivity() { private lateinit var binding: ActivityNotificationPermissionBinding - private val viewModel by viewModels() + private val viewModel = getViewModel() private var adapter: NotificationPermissionListAdapter? = null private var isFirstCheck = true @@ -120,7 +120,7 @@ class NotificationPermissionActivity : AppCompatActivity() { } private fun displayAutoSynchroPopup() { - val autoSynchroValue = SharedPreferencesManager.readString(this, SharedPreferencesManager.SharedPrefKey.AUTO_SYNCHRO) + val autoSynchroValue = SharedPreferencesManager.readString(SharedPreferencesManager.SharedPrefKey.AUTO_SYNCHRO) if (autoSynchroValue.toFloat() <= 0) { MaterialDialog.Builder(this) diff --git a/app/src/main/java/com/readrops/app/activities/SplashActivity.java b/app/src/main/java/com/readrops/app/activities/SplashActivity.java index ef74650b..33edd643 100644 --- a/app/src/main/java/com/readrops/app/activities/SplashActivity.java +++ b/app/src/main/java/com/readrops/app/activities/SplashActivity.java @@ -4,11 +4,12 @@ import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProvider; import com.readrops.app.R; import com.readrops.app.viewmodels.AccountViewModel; +import org.koin.androidx.viewmodel.compat.ViewModelCompat; + import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.observers.DisposableSingleObserver; import io.reactivex.schedulers.Schedulers; @@ -22,7 +23,7 @@ public class SplashActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); - viewModel = new ViewModelProvider(this).get(AccountViewModel.class); + viewModel = ViewModelCompat.getViewModel(this, AccountViewModel.class); viewModel.getAccountCount() .subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/com/readrops/app/adapters/FeedsAdapter.java b/app/src/main/java/com/readrops/app/adapters/FeedsAdapter.java index 385813e4..9363b85e 100644 --- a/app/src/main/java/com/readrops/app/adapters/FeedsAdapter.java +++ b/app/src/main/java/com/readrops/app/adapters/FeedsAdapter.java @@ -13,9 +13,11 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.readrops.app.R; import com.readrops.app.databinding.FeedLayoutBinding; -import com.readrops.app.utils.GlideApp; +import com.readrops.app.utils.GlideRequests; import com.readrops.db.pojo.FeedWithFolder; +import org.koin.java.KoinJavaComponent; + import java.util.List; public class FeedsAdapter extends ListAdapter { @@ -64,7 +66,7 @@ public class FeedsAdapter extends ListAdapter Unit) : - ListAdapter(DIFF_CALLBACK) { + ListAdapter(DIFF_CALLBACK), KoinComponent { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationPermissionViewHolder { val binding = NotificationPermissionLayoutBinding.inflate(LayoutInflater.from(parent.context)) @@ -30,7 +33,7 @@ class NotificationPermissionListAdapter(var enableAll: Boolean, val listener: (f holder.itemView.setOnClickListener { if (enableAll) listener(getItem(position)) } - GlideApp.with(holder.itemView.context) + get() .load(feed.iconUrl) .diskCacheStrategy(DiskCacheStrategy.ALL) .placeholder(R.drawable.ic_rss_feed_grey) diff --git a/app/src/main/java/com/readrops/app/fragments/EditFeedDialogFragment.java b/app/src/main/java/com/readrops/app/fragments/EditFeedDialogFragment.java index 16bfc365..5fc3c2f6 100644 --- a/app/src/main/java/com/readrops/app/fragments/EditFeedDialogFragment.java +++ b/app/src/main/java/com/readrops/app/fragments/EditFeedDialogFragment.java @@ -11,7 +11,6 @@ import android.widget.Spinner; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; -import androidx.lifecycle.ViewModelProvider; import com.google.android.material.textfield.TextInputEditText; import com.readrops.app.R; @@ -21,6 +20,8 @@ import com.readrops.db.entities.Folder; import com.readrops.db.entities.account.Account; import com.readrops.db.pojo.FeedWithFolder; +import org.koin.androidx.viewmodel.compat.SharedViewModelCompat; + import java.util.ArrayList; import java.util.Map; import java.util.TreeMap; @@ -59,7 +60,7 @@ public class EditFeedDialogFragment extends DialogFragment implements AdapterVie @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - viewModel = new ViewModelProvider(getActivity()).get(ManageFeedsFoldersViewModel.class); + viewModel = SharedViewModelCompat.getSharedViewModel(this, ManageFeedsFoldersViewModel.class); feedWithFolder = getArguments().getParcelable("feedWithFolder"); account = getArguments().getParcelable(ACCOUNT); diff --git a/app/src/main/java/com/readrops/app/fragments/FeedsFragment.java b/app/src/main/java/com/readrops/app/fragments/FeedsFragment.java index f07f66b9..ff9a31e2 100644 --- a/app/src/main/java/com/readrops/app/fragments/FeedsFragment.java +++ b/app/src/main/java/com/readrops/app/fragments/FeedsFragment.java @@ -10,7 +10,6 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import com.afollestad.materialdialogs.MaterialDialog; @@ -24,6 +23,8 @@ import com.readrops.db.entities.Feed; import com.readrops.db.entities.account.Account; import com.readrops.db.pojo.FeedWithFolder; +import org.koin.androidx.viewmodel.compat.SharedViewModelCompat; + import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.observers.DisposableCompletableObserver; import io.reactivex.schedulers.Schedulers; @@ -60,11 +61,11 @@ public class FeedsFragment extends Fragment { account = getArguments().getParcelable(ACCOUNT); if (account.getLogin() == null) - account.setLogin(SharedPreferencesManager.readString(getContext(), account.getLoginKey())); + account.setLogin(SharedPreferencesManager.readString(account.getLoginKey())); if (account.getPassword() == null) - account.setPassword(SharedPreferencesManager.readString(getContext(), account.getPasswordKey())); + account.setPassword(SharedPreferencesManager.readString(account.getPasswordKey())); - viewModel = new ViewModelProvider(this).get(ManageFeedsFoldersViewModel.class); + viewModel = SharedViewModelCompat.getSharedViewModel(this, ManageFeedsFoldersViewModel.class); viewModel.setAccount(account); viewModel.getFeedsWithFolder().observe(this, feedWithFolders -> { diff --git a/app/src/main/java/com/readrops/app/fragments/FoldersFragment.java b/app/src/main/java/com/readrops/app/fragments/FoldersFragment.java index 9019a09c..587660ee 100644 --- a/app/src/main/java/com/readrops/app/fragments/FoldersFragment.java +++ b/app/src/main/java/com/readrops/app/fragments/FoldersFragment.java @@ -10,10 +10,11 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import com.afollestad.materialdialogs.MaterialDialog; +import com.readrops.api.utils.ConflictException; +import com.readrops.api.utils.UnknownFormatException; import com.readrops.app.R; import com.readrops.app.adapters.FoldersAdapter; import com.readrops.app.databinding.FragmentFoldersBinding; @@ -22,8 +23,8 @@ import com.readrops.app.utils.Utils; import com.readrops.app.viewmodels.ManageFeedsFoldersViewModel; import com.readrops.db.entities.Folder; import com.readrops.db.entities.account.Account; -import com.readrops.api.utils.ConflictException; -import com.readrops.api.utils.UnknownFormatException; + +import org.koin.androidx.viewmodel.compat.SharedViewModelCompat; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.observers.DisposableSingleObserver; @@ -60,12 +61,12 @@ public class FoldersFragment extends Fragment { account = getArguments().getParcelable(ACCOUNT); if (account.getLogin() == null) - account.setLogin(SharedPreferencesManager.readString(getContext(), account.getLoginKey())); + account.setLogin(SharedPreferencesManager.readString(account.getLoginKey())); if (account.getPassword() == null) - account.setPassword(SharedPreferencesManager.readString(getContext(), account.getPasswordKey())); + account.setPassword(SharedPreferencesManager.readString(account.getPasswordKey())); adapter = new FoldersAdapter(this::openFolderOptionsDialog); - viewModel = new ViewModelProvider(this).get(ManageFeedsFoldersViewModel.class); + viewModel = SharedViewModelCompat.getSharedViewModel(this, ManageFeedsFoldersViewModel.class); viewModel.setAccount(account); viewModel.getFeedCountByAccount() diff --git a/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java b/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java index 71f8867b..b0f1cc95 100644 --- a/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java +++ b/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java @@ -16,7 +16,6 @@ import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; @@ -36,6 +35,8 @@ import com.readrops.app.viewmodels.AccountViewModel; import com.readrops.db.entities.account.Account; import com.readrops.db.entities.account.AccountType; +import org.koin.androidx.viewmodel.compat.ViewModelCompat; + import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.observers.DisposableCompletableObserver; import io.reactivex.schedulers.Schedulers; @@ -136,7 +137,7 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - viewModel = new ViewModelProvider(this).get(AccountViewModel.class); + viewModel = ViewModelCompat.getViewModel(this, AccountViewModel.class); viewModel.setAccount(account); } @@ -146,8 +147,8 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat { .positiveText(R.string.validate) .negativeText(R.string.cancel) .onPositive(((dialog, which) -> { - SharedPreferencesManager.remove(getContext(), account.getLoginKey()); - SharedPreferencesManager.remove(getContext(), account.getPasswordKey()); + SharedPreferencesManager.remove(account.getLoginKey()); + SharedPreferencesManager.remove(account.getPasswordKey()); viewModel.delete(account) .subscribeOn(Schedulers.io()) @@ -204,7 +205,7 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat { } private void parseOPMLFile(Uri uri, MaterialDialog dialog) { - viewModel.parseOPMLFile(uri) + viewModel.parseOPMLFile(uri, getContext()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new DisposableCompletableObserver() { diff --git a/app/src/main/java/com/readrops/app/fragments/settings/SettingsFragment.java b/app/src/main/java/com/readrops/app/fragments/settings/SettingsFragment.java index ce84fd77..4a28bad7 100644 --- a/app/src/main/java/com/readrops/app/fragments/settings/SettingsFragment.java +++ b/app/src/main/java/com/readrops/app/fragments/settings/SettingsFragment.java @@ -19,6 +19,8 @@ import com.readrops.app.utils.SyncWorker; import com.readrops.app.utils.feedscolors.FeedsColorsIntentService; import com.readrops.db.Database; +import org.koin.java.KoinJavaComponent; + import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -38,7 +40,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { AtomicBoolean serviceStarted = new AtomicBoolean(false); feedsColorsPreference.setOnPreferenceClickListener(preference -> { - Database database = Database.getInstance(getContext()); + Database database = KoinJavaComponent.get(Database.class); database.feedDao().getAllFeeds().observe(getActivity(), feeds -> { if (!serviceStarted.get()) { diff --git a/app/src/main/java/com/readrops/app/repositories/ARepository.java b/app/src/main/java/com/readrops/app/repositories/ARepository.java index faa55acb..47425232 100644 --- a/app/src/main/java/com/readrops/app/repositories/ARepository.java +++ b/app/src/main/java/com/readrops/app/repositories/ARepository.java @@ -6,6 +6,9 @@ import android.content.Intent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.readrops.api.services.Credentials; +import com.readrops.api.services.SyncResult; +import com.readrops.api.utils.AuthInterceptor; import com.readrops.app.utils.FeedInsertionResult; import com.readrops.app.utils.ParsingResult; import com.readrops.app.utils.feedscolors.FeedColorsKt; @@ -15,8 +18,8 @@ import com.readrops.db.entities.Feed; import com.readrops.db.entities.Folder; import com.readrops.db.entities.Item; import com.readrops.db.entities.account.Account; -import com.readrops.db.entities.account.AccountType; -import com.readrops.api.services.SyncResult; + +import org.koin.java.KoinJavaComponent; import java.util.ArrayList; import java.util.Comparator; @@ -30,25 +33,26 @@ import io.reactivex.Single; import static com.readrops.app.utils.ReadropsKeys.FEEDS; -public abstract class ARepository { +public abstract class ARepository { protected Context context; protected Database database; protected Account account; - protected T api; - protected SyncResult syncResult; - protected ARepository(@NonNull Context context, @Nullable Account account) { + protected ARepository(Database database, @NonNull Context context, @Nullable Account account) { this.context = context; - this.database = Database.getInstance(context); + this.database = database; this.account = account; - api = createAPI(); + setCredentials(account); } - protected abstract T createAPI(); + protected void setCredentials(@Nullable Account account) { + KoinJavaComponent.get(AuthInterceptor.class) + .setCredentials(account != null && !account.isLocal() ? Credentials.toCredentials(account) : null); + } // TODO : replace Single by Completable public abstract Single login(Account account, boolean insert); @@ -171,23 +175,6 @@ public abstract class ARepository { context.startService(intent); } - public static ARepository repositoryFactory(Account account, AccountType accountType, Context context) throws Exception { - switch (accountType) { - case LOCAL: - return new LocalFeedRepository(context, account); - case NEXTCLOUD_NEWS: - return new NextNewsRepository(context, account); - case FRESHRSS: - return new FreshRSSRepository(context, account); - default: - throw new Exception("account type not supported"); - } - } - - public static ARepository repositoryFactory(Account account, Context context) throws Exception { - return ARepository.repositoryFactory(account, account.getAccountType(), context); - } - public SyncResult getSyncResult() { return syncResult; } diff --git a/app/src/main/java/com/readrops/app/repositories/FreshRSSRepository.java b/app/src/main/java/com/readrops/app/repositories/FreshRSSRepository.java index 744e80f6..0ab8a89c 100644 --- a/app/src/main/java/com/readrops/app/repositories/FreshRSSRepository.java +++ b/app/src/main/java/com/readrops/app/repositories/FreshRSSRepository.java @@ -7,18 +7,17 @@ import android.util.TimingLogger; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.readrops.api.services.SyncType; +import com.readrops.api.services.freshrss.FreshRSSDataSource; +import com.readrops.api.services.freshrss.FreshRSSSyncData; import com.readrops.app.utils.FeedInsertionResult; import com.readrops.app.utils.ParsingResult; import com.readrops.app.utils.Utils; +import com.readrops.db.Database; import com.readrops.db.entities.Feed; import com.readrops.db.entities.Folder; import com.readrops.db.entities.Item; import com.readrops.db.entities.account.Account; -import com.readrops.api.services.Credentials; -import com.readrops.api.services.SyncType; -import com.readrops.api.services.freshrss.FreshRSSAPI; -import com.readrops.api.services.freshrss.FreshRSSCredentials; -import com.readrops.api.services.freshrss.FreshRSSSyncData; import org.joda.time.DateTime; @@ -30,40 +29,33 @@ import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.Single; -public class FreshRSSRepository extends ARepository { +public class FreshRSSRepository extends ARepository { private static final String TAG = FreshRSSRepository.class.getSimpleName(); - public FreshRSSRepository(@NonNull Context context, @Nullable Account account) { - super(context, account); - } + private final FreshRSSDataSource dataSource; - @Override - protected FreshRSSAPI createAPI() { - if (account != null) - return new FreshRSSAPI(Credentials.toCredentials(account)); + public FreshRSSRepository(FreshRSSDataSource dataSource, Database database, @NonNull Context context, @Nullable Account account) { + super(database, context, account); - return null; + this.dataSource = dataSource; } @Override public Single login(Account account, boolean insert) { - if (api == null) - api = new FreshRSSAPI(Credentials.toCredentials(account)); - else - api.setCredentials(Credentials.toCredentials(account)); + setCredentials(account); - return api.login(account.getLogin(), account.getPassword()) + return dataSource.login(account.getLogin(), account.getPassword()) .flatMap(token -> { account.setToken(token); - api.setCredentials(new FreshRSSCredentials(token, account.getUrl())); + setCredentials(account); - return api.getWriteToken(); + return dataSource.getWriteToken(); }) .flatMap(writeToken -> { account.setWriteToken(writeToken); - return api.getUserInfo(); + return dataSource.getUserInfo(); }) .flatMap(userInfo -> { account.setDisplayedName(userInfo.getUserName()); @@ -100,7 +92,7 @@ public class FreshRSSRepository extends ARepository { syncData.setUnreadItemsIds(database.itemDao().getUnreadChanges(account.getId())); emitter.onSuccess(syncData); - }).flatMap(syncData1 -> api.sync(syncType, syncData1, account.getWriteToken())) + }).flatMap(syncData1 -> dataSource.sync(syncType, syncData1, account.getWriteToken())) .flatMapObservable(syncResult -> { logger.addSplit("server queries"); @@ -131,7 +123,7 @@ public class FreshRSSRepository extends ARepository { List insertionResults = new ArrayList<>(); for (ParsingResult result : results) { - completableList.add(api.createFeed(account.getWriteToken(), result.getUrl()) + completableList.add(dataSource.createFeed(account.getWriteToken(), result.getUrl()) .doOnComplete(() -> { FeedInsertionResult feedInsertionResult = new FeedInsertionResult(); feedInsertionResult.setParsingResult(result); @@ -159,26 +151,26 @@ public class FreshRSSRepository extends ARepository { Folder folder = feed.getFolderId() == null ? null : database.folderDao().select(feed.getFolderId()); emitter.onSuccess(folder); - }).flatMapCompletable(folder -> api.updateFeed(account.getWriteToken(), + }).flatMapCompletable(folder -> dataSource.updateFeed(account.getWriteToken(), feed.getUrl(), feed.getName(), folder == null ? null : folder.getRemoteId()) .andThen(super.updateFeed(feed))); } @Override public Completable deleteFeed(Feed feed) { - return api.deleteFeed(account.getWriteToken(), feed.getUrl()) + return dataSource.deleteFeed(account.getWriteToken(), feed.getUrl()) .andThen(super.deleteFeed(feed)); } @Override public Single addFolder(Folder folder) { - return api.createFolder(account.getWriteToken(), folder.getName()) + return dataSource.createFolder(account.getWriteToken(), folder.getName()) .andThen(super.addFolder(folder)); } @Override public Completable updateFolder(Folder folder) { - return api.updateFolder(account.getWriteToken(), folder.getRemoteId(), folder.getName()) + return dataSource.updateFolder(account.getWriteToken(), folder.getRemoteId(), folder.getName()) .andThen(Completable.create(emitter -> { folder.setRemoteId("user/-/label/" + folder.getName()); emitter.onComplete(); @@ -188,7 +180,7 @@ public class FreshRSSRepository extends ARepository { @Override public Completable deleteFolder(Folder folder) { - return api.deleteFolder(account.getWriteToken(), folder.getRemoteId()) + return dataSource.deleteFolder(account.getWriteToken(), folder.getRemoteId()) .andThen(super.deleteFolder(folder)); } diff --git a/app/src/main/java/com/readrops/app/repositories/LocalFeedRepository.java b/app/src/main/java/com/readrops/app/repositories/LocalFeedRepository.java index f6b05c3b..f1d27653 100644 --- a/app/src/main/java/com/readrops/app/repositories/LocalFeedRepository.java +++ b/app/src/main/java/com/readrops/app/repositories/LocalFeedRepository.java @@ -9,7 +9,6 @@ import androidx.annotation.Nullable; import com.readrops.api.localfeed.LocalRSSDataSource; import com.readrops.api.services.SyncResult; -import com.readrops.api.utils.HttpManager; import com.readrops.api.utils.LibUtils; import com.readrops.api.utils.ParseException; import com.readrops.api.utils.UnknownFormatException; @@ -17,6 +16,7 @@ import com.readrops.app.utils.FeedInsertionResult; import com.readrops.app.utils.ParsingResult; import com.readrops.app.utils.SharedPreferencesManager; import com.readrops.app.utils.Utils; +import com.readrops.db.Database; import com.readrops.db.entities.Feed; import com.readrops.db.entities.Item; import com.readrops.db.entities.account.Account; @@ -34,22 +34,17 @@ import io.reactivex.Single; import kotlin.Pair; import okhttp3.Headers; -public class LocalFeedRepository extends ARepository { +public class LocalFeedRepository extends ARepository { private static final String TAG = LocalFeedRepository.class.getSimpleName(); private LocalRSSDataSource dataSource; - public LocalFeedRepository(@NonNull Context context, @Nullable Account account) { - super(context, account); + public LocalFeedRepository(LocalRSSDataSource dataSource, Database database, @NonNull Context context, @Nullable Account account) { + super(database, context, account); syncResult = new SyncResult(); - dataSource = new LocalRSSDataSource(HttpManager.getInstance().getOkHttpClient()); - } - - @Override - protected Void createAPI() { - return null; + this.dataSource = dataSource; } @Override @@ -138,7 +133,7 @@ public class LocalFeedRepository extends ARepository { Collections.sort(items, Item::compareTo); - int maxItems = Integer.parseInt(SharedPreferencesManager.readString(context, + int maxItems = Integer.parseInt(SharedPreferencesManager.readString( SharedPreferencesManager.SharedPrefKey.ITEMS_TO_PARSE_MAX_NB)); if (maxItems > 0 && items.size() > maxItems) { items = items.subList(items.size() - maxItems, items.size()); diff --git a/app/src/main/java/com/readrops/app/repositories/NextNewsRepository.java b/app/src/main/java/com/readrops/app/repositories/NextNewsRepository.java index 018d6a7d..1d10f93b 100644 --- a/app/src/main/java/com/readrops/app/repositories/NextNewsRepository.java +++ b/app/src/main/java/com/readrops/app/repositories/NextNewsRepository.java @@ -7,16 +7,16 @@ import android.util.TimingLogger; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.readrops.api.services.Credentials; import com.readrops.api.services.SyncResult; import com.readrops.api.services.SyncType; -import com.readrops.api.services.nextcloudnews.NextNewsAPI; +import com.readrops.api.services.nextcloudnews.NextNewsDataSource; import com.readrops.api.services.nextcloudnews.NextNewsSyncData; import com.readrops.api.services.nextcloudnews.json.NextNewsUser; import com.readrops.api.utils.UnknownFormatException; import com.readrops.app.utils.FeedInsertionResult; import com.readrops.app.utils.ParsingResult; import com.readrops.app.utils.Utils; +import com.readrops.db.Database; import com.readrops.db.entities.Feed; import com.readrops.db.entities.Folder; import com.readrops.db.entities.Item; @@ -33,31 +33,23 @@ import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.Single; -public class NextNewsRepository extends ARepository { +public class NextNewsRepository extends ARepository { private static final String TAG = NextNewsRepository.class.getSimpleName(); - public NextNewsRepository(@NonNull Context context, @Nullable Account account) { - super(context, account); - } + private final NextNewsDataSource dataSource; - @Override - protected NextNewsAPI createAPI() { - if (account != null) - return new NextNewsAPI(Credentials.toCredentials(account)); + public NextNewsRepository(NextNewsDataSource dataSource, Database database, @NonNull Context context, @Nullable Account account) { + super(database, context, account); - return null; + this.dataSource = dataSource; } @Override public Single login(Account account, boolean insert) { + setCredentials(account); return Single.create(emitter -> { - if (api == null) - api = new NextNewsAPI(Credentials.toCredentials(account)); - else - api.setCredentials(Credentials.toCredentials(account)); - - NextNewsUser user = api.login(); + NextNewsUser user = dataSource.login(); if (user != null) { emitter.onSuccess(user); @@ -101,7 +93,7 @@ public class NextNewsRepository extends ARepository { } TimingLogger timings = new TimingLogger(TAG, "nextcloud news " + syncType.name().toLowerCase()); - SyncResult result = api.sync(syncType, syncData); + SyncResult result = dataSource.sync(syncType, syncData); timings.addSplit("server queries"); if (!result.isError()) { @@ -141,11 +133,11 @@ public class NextNewsRepository extends ARepository { FeedInsertionResult insertionResult = new FeedInsertionResult(); try { - List nextNewsFeeds = api.createFeed(result.getUrl(), 0); + List nextNewsFeeds = dataSource.createFeed(result.getUrl(), 0); if (nextNewsFeeds != null) { List newFeeds = insertFeeds(nextNewsFeeds, true); - // there is always only one object in the list, see nextcloud news api doc + // there is always only one object in the list, see nextcloud news dataSource doc insertionResult.setFeed(newFeeds.get(0)); } else insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.UNKNOWN_ERROR); @@ -180,7 +172,7 @@ public class NextNewsRepository extends ARepository { feed.setRemoteFolderId(String.valueOf(0)); // 0 for no folder try { - if (api.renameFeed(feed) && api.changeFeedFolder(feed)) { + if (dataSource.renameFeed(feed) && dataSource.changeFeedFolder(feed)) { emitter.onComplete(); } else emitter.onError(new Exception("Unknown error when updating feed")); @@ -194,7 +186,7 @@ public class NextNewsRepository extends ARepository { public Completable deleteFeed(Feed feed) { return Completable.create(emitter -> { try { - if (api.deleteFeed(Integer.parseInt(feed.getRemoteId()))) { + if (dataSource.deleteFeed(Integer.parseInt(feed.getRemoteId()))) { emitter.onComplete(); } else emitter.onError(new Exception("Unknown error")); @@ -210,7 +202,7 @@ public class NextNewsRepository extends ARepository { public Single addFolder(Folder folder) { return Single.create(emitter -> { try { - List folders = api.createFolder(folder); + List folders = dataSource.createFolder(folder); if (folders != null) { Folder nextNewsFolder = folders.get(0); // always only one item returned by the server, see doc @@ -229,7 +221,7 @@ public class NextNewsRepository extends ARepository { public Completable updateFolder(Folder folder) { return Completable.create(emitter -> { try { - if (api.renameFolder(folder)) { + if (dataSource.renameFolder(folder)) { emitter.onComplete(); } else emitter.onError(new Exception("Unknown error")); @@ -246,7 +238,7 @@ public class NextNewsRepository extends ARepository { public Completable deleteFolder(Folder folder) { return Completable.create(emitter -> { try { - if (api.deleteFolder(folder)) { + if (dataSource.deleteFolder(folder)) { emitter.onComplete(); } else emitter.onError(new Exception("Unknown error")); diff --git a/app/src/main/java/com/readrops/app/utils/HtmlParser.java b/app/src/main/java/com/readrops/app/utils/HtmlParser.java index 369244e6..daebc212 100644 --- a/app/src/main/java/com/readrops/app/utils/HtmlParser.java +++ b/app/src/main/java/com/readrops/app/utils/HtmlParser.java @@ -6,18 +6,19 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.readrops.api.localfeed.LocalRSSHelper; -import com.readrops.api.utils.HttpManager; import com.readrops.api.utils.LibUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import org.koin.java.KoinJavaComponent; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -83,7 +84,7 @@ public final class HtmlParser { long start = System.currentTimeMillis(); try { - Response response = HttpManager.getInstance().getOkHttpClient() + Response response = KoinJavaComponent.get(OkHttpClient.class) .newCall(new Request.Builder().url(url).build()).execute(); if (response.header("Content-Type").contains(LibUtils.HTML_CONTENT_TYPE)) { @@ -98,6 +99,7 @@ public final class HtmlParser { return null; } } catch (Exception e) { + Log.d(TAG, e.getMessage()); return null; } diff --git a/app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt b/app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt index 3138c7dd..621e241e 100644 --- a/app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt +++ b/app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt @@ -7,15 +7,17 @@ import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.module.AppGlideModule -import com.readrops.api.utils.HttpManager +import okhttp3.OkHttpClient +import org.koin.core.KoinComponent +import org.koin.core.get import java.io.InputStream @GlideModule -class ReadropsGlideModule : AppGlideModule() { +class ReadropsGlideModule : AppGlideModule(), KoinComponent { override fun registerComponents(context: Context, glide: Glide, registry: Registry) { - val factory = OkHttpUrlLoader.Factory(HttpManager.getInstance().okHttpClient) - + val factory = OkHttpUrlLoader.Factory(get()) + glide.registry.replace(GlideUrl::class.java, InputStream::class.java, factory) } } \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/utils/SharedPreferencesManager.java b/app/src/main/java/com/readrops/app/utils/SharedPreferencesManager.java index 0da4ee57..9d42e45b 100644 --- a/app/src/main/java/com/readrops/app/utils/SharedPreferencesManager.java +++ b/app/src/main/java/com/readrops/app/utils/SharedPreferencesManager.java @@ -1,19 +1,15 @@ package com.readrops.app.utils; -import android.content.Context; import android.content.SharedPreferences; -import android.preference.PreferenceManager; import androidx.annotation.NonNull; +import org.koin.java.KoinJavaComponent; + public final class SharedPreferencesManager { - private static SharedPreferences getSharedPreferences(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context); - } - - public static void writeValue(Context context, String key, Object value) { - SharedPreferences sharedPref = getSharedPreferences(context); + public static void writeValue(String key, Object value) { + SharedPreferences sharedPref = KoinJavaComponent.get(SharedPreferences.class); SharedPreferences.Editor editor = sharedPref.edit(); if (value instanceof Boolean) @@ -24,32 +20,32 @@ public final class SharedPreferencesManager { editor.apply(); } - public static void writeValue(Context context, SharedPrefKey sharedPrefKey, Object value) { - writeValue(context, sharedPrefKey.key, value); + public static void writeValue(SharedPrefKey sharedPrefKey, Object value) { + writeValue(sharedPrefKey.key, value); } - public static int readInt(Context context, SharedPrefKey sharedPrefKey) { - SharedPreferences sharedPreferences = getSharedPreferences(context); + public static int readInt(SharedPrefKey sharedPrefKey) { + SharedPreferences sharedPreferences = KoinJavaComponent.get(SharedPreferences.class); return sharedPreferences.getInt(sharedPrefKey.key, sharedPrefKey.getIntDefaultValue()); } - public static boolean readBoolean(Context context, SharedPrefKey sharedPrefKey) { - SharedPreferences sharedPreferences = getSharedPreferences(context); + public static boolean readBoolean(SharedPrefKey sharedPrefKey) { + SharedPreferences sharedPreferences = KoinJavaComponent.get(SharedPreferences.class); return sharedPreferences.getBoolean(sharedPrefKey.key, sharedPrefKey.getBooleanDefaultValue()); } - public static String readString(Context context, String key) { - SharedPreferences sharedPreferences = getSharedPreferences(context); + public static String readString(String key) { + SharedPreferences sharedPreferences = KoinJavaComponent.get(SharedPreferences.class); return sharedPreferences.getString(key, null); } - public static String readString(Context context, SharedPrefKey sharedPrefKey) { - SharedPreferences sharedPreferences = getSharedPreferences(context); + public static String readString(SharedPrefKey sharedPrefKey) { + SharedPreferences sharedPreferences = KoinJavaComponent.get(SharedPreferences.class); return sharedPreferences.getString(sharedPrefKey.key, sharedPrefKey.getStringDefaultValue()); } - public static void remove(Context context, String key) { - SharedPreferences sharedPreferences = getSharedPreferences(context); + public static void remove(String key) { + SharedPreferences sharedPreferences = KoinJavaComponent.get(SharedPreferences.class); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.remove(key); diff --git a/app/src/main/java/com/readrops/app/utils/SyncResultAnalyser.kt b/app/src/main/java/com/readrops/app/utils/SyncResultAnalyser.kt index 497c24fe..a95c37dd 100644 --- a/app/src/main/java/com/readrops/app/utils/SyncResultAnalyser.kt +++ b/app/src/main/java/com/readrops/app/utils/SyncResultAnalyser.kt @@ -9,11 +9,13 @@ import com.readrops.db.entities.Feed import com.readrops.db.entities.Item import com.readrops.db.entities.account.Account import com.readrops.api.services.SyncResult +import org.koin.core.KoinComponent +import org.koin.core.get /** * Simple class to get synchro notification content (title, content and largeIcon) according to some rules */ -class SyncResultAnalyser(val context: Context, private val syncResults: Map, val database: Database) { +class SyncResultAnalyser(val context: Context, private val syncResults: Map, val database: Database) : KoinComponent { private val notifContent = SyncResultNotifContent() @@ -66,7 +68,7 @@ class SyncResultAnalyser(val context: Context, private val syncResults: Map() .asBitmap() .load(it) .diskCacheStrategy(DiskCacheStrategy.ALL) diff --git a/app/src/main/java/com/readrops/app/utils/SyncResultDebugData.kt b/app/src/main/java/com/readrops/app/utils/SyncResultDebugData.kt index 5018b464..e80de538 100644 --- a/app/src/main/java/com/readrops/app/utils/SyncResultDebugData.kt +++ b/app/src/main/java/com/readrops/app/utils/SyncResultDebugData.kt @@ -1,20 +1,22 @@ package com.readrops.app.utils import android.content.Context +import com.readrops.api.services.SyncResult import com.readrops.db.Database import com.readrops.db.entities.Item import com.readrops.db.entities.account.Account import com.readrops.db.entities.account.AccountType -import com.readrops.api.services.SyncResult import org.jetbrains.annotations.TestOnly +import org.koin.core.KoinComponent +import org.koin.core.get class SyncResultDebugData { - companion object { + companion object : KoinComponent { @TestOnly - fun oneAccountOneFeedOneItem(context: Context): Map { - val database = Database.getInstance(context) + fun oneAccountOneFeedOneItem(): Map { + val database = get() val account1 = database.accountDao().select(2) @@ -27,14 +29,14 @@ class SyncResultDebugData { } @TestOnly - fun oneAccountOneFeedMultipleItems(context: Context): Map { + fun oneAccountOneFeedMultipleItems(): Map { val account1 = Account().apply { id = 1 accountType = AccountType.FRESHRSS isNotificationsEnabled = true } - val database = Database.getInstance(context) + val database = get() val item = database.itemDao().select(5055) database.feedDao().updateFeedNotificationState(item.feedId, false).subscribe() diff --git a/app/src/main/java/com/readrops/app/utils/SyncWorker.kt b/app/src/main/java/com/readrops/app/utils/SyncWorker.kt index 474b189d..e788690b 100644 --- a/app/src/main/java/com/readrops/app/utils/SyncWorker.kt +++ b/app/src/main/java/com/readrops/app/utils/SyncWorker.kt @@ -19,13 +19,16 @@ import com.readrops.db.entities.Item import com.readrops.db.entities.account.Account import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import org.koin.core.KoinComponent +import org.koin.core.get +import org.koin.core.parameter.parametersOf -class SyncWorker(context: Context, parameters: WorkerParameters) : Worker(context, parameters) { +class SyncWorker(context: Context, parameters: WorkerParameters) : Worker(context, parameters), KoinComponent { private var disposable: Disposable? = null private val notificationManager = NotificationManagerCompat.from(applicationContext) - private val database = Database.getInstance(applicationContext) + private val database = get() override fun doWork(): Result { var result = Result.success() @@ -39,15 +42,15 @@ class SyncWorker(context: Context, parameters: WorkerParameters) : Worker(contex .setProgress(0, 0, true) .setSmallIcon(R.drawable.ic_notif) .setOnlyAlertOnce(true) - + accounts.forEach { notificationBuilder.setContentText(it.accountName) notificationManager.notify(SYNC_NOTIFICATION_ID, notificationBuilder.build()) - it.login = SharedPreferencesManager.readString(applicationContext, it.loginKey) - it.password = SharedPreferencesManager.readString(applicationContext, it.passwordKey) + it.login = SharedPreferencesManager.readString(it.loginKey) + it.password = SharedPreferencesManager.readString(it.passwordKey) - val repository = ARepository.repositoryFactory(it, applicationContext) + val repository = get(parameters = { parametersOf(it) }) disposable = repository.sync(null) .doOnError { throwable -> @@ -138,12 +141,12 @@ class SyncWorker(context: Context, parameters: WorkerParameters) : Worker(contex .build() } - class MarkReadReceiver : BroadcastReceiver() { + class MarkReadReceiver : BroadcastReceiver(), KoinComponent { override fun onReceive(context: Context?, intent: Intent?) { val itemId = intent?.getIntExtra(ReadropsKeys.ITEM_ID, 0)!! - with(Database.getInstance(context)) { + with(get()) { itemDao().setReadState(itemId, true, true) .subscribeOn(Schedulers.io()) .subscribe() @@ -155,12 +158,12 @@ class SyncWorker(context: Context, parameters: WorkerParameters) : Worker(contex } } - class ReadLaterReceiver : BroadcastReceiver() { + class ReadLaterReceiver : BroadcastReceiver(), KoinComponent { override fun onReceive(context: Context?, intent: Intent?) { val itemId = intent?.getIntExtra(ReadropsKeys.ITEM_ID, 0)!! - with(Database.getInstance(context)) { + with(get()) { itemDao().setReadItLater(itemId) .subscribeOn(Schedulers.io()) .subscribe() diff --git a/app/src/main/java/com/readrops/app/utils/Utils.java b/app/src/main/java/com/readrops/app/utils/Utils.java index 7da6514a..aad043b1 100644 --- a/app/src/main/java/com/readrops/app/utils/Utils.java +++ b/app/src/main/java/com/readrops/app/utils/Utils.java @@ -15,7 +15,8 @@ import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import com.google.android.material.snackbar.Snackbar; -import com.readrops.api.utils.HttpManager; + +import org.koin.java.KoinJavaComponent; import java.io.InputStream; import java.util.Locale; @@ -34,10 +35,9 @@ public final class Utils { public static Bitmap getImageFromUrl(String url) { try { - OkHttpClient okHttpClient = HttpManager.getInstance().getOkHttpClient(); Request request = new Request.Builder().url(url).build(); - Response response = okHttpClient.newCall(request).execute(); + Response response = KoinJavaComponent.get(OkHttpClient.class).newCall(request).execute(); if (response.isSuccessful()) { InputStream inputStream = response.body().byteStream(); diff --git a/app/src/main/java/com/readrops/app/utils/feedscolors/FeedsColorsIntentService.kt b/app/src/main/java/com/readrops/app/utils/feedscolors/FeedsColorsIntentService.kt index 880eb4c9..6fbf9912 100644 --- a/app/src/main/java/com/readrops/app/utils/feedscolors/FeedsColorsIntentService.kt +++ b/app/src/main/java/com/readrops/app/utils/feedscolors/FeedsColorsIntentService.kt @@ -6,15 +6,17 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.readrops.app.R import com.readrops.app.ReadropsApp +import com.readrops.app.utils.ReadropsKeys.FEEDS import com.readrops.db.Database import com.readrops.db.entities.Feed -import com.readrops.app.utils.ReadropsKeys.FEEDS +import org.koin.core.KoinComponent +import org.koin.core.get -class FeedsColorsIntentService : IntentService("FeedsColorsIntentService") { +class FeedsColorsIntentService : IntentService("FeedsColorsIntentService"), KoinComponent { override fun onHandleIntent(intent: Intent?) { val feeds: List = intent!!.getParcelableArrayListExtra(FEEDS)!! - val database = Database.getInstance(this) + val database = get() val notificationBuilder = NotificationCompat.Builder(this, ReadropsApp.FEEDS_COLORS_CHANNEL_ID) .setContentTitle(getString(R.string.get_feeds_colors)) diff --git a/app/src/main/java/com/readrops/app/viewmodels/AccountViewModel.java b/app/src/main/java/com/readrops/app/viewmodels/AccountViewModel.java index b34aa46c..e93677e0 100644 --- a/app/src/main/java/com/readrops/app/viewmodels/AccountViewModel.java +++ b/app/src/main/java/com/readrops/app/viewmodels/AccountViewModel.java @@ -1,11 +1,10 @@ package com.readrops.app.viewmodels; -import android.app.Application; +import android.content.Context; import android.net.Uri; -import android.util.Log; import androidx.annotation.NonNull; -import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.ViewModel; import com.readrops.api.opml.OPMLParser; import com.readrops.app.repositories.ARepository; @@ -15,35 +14,32 @@ import com.readrops.db.entities.Folder; import com.readrops.db.entities.account.Account; import com.readrops.db.entities.account.AccountType; +import org.koin.core.parameter.DefinitionParametersKt; +import org.koin.java.KoinJavaComponent; + import java.util.List; import java.util.Map; import io.reactivex.Completable; import io.reactivex.Single; -public class AccountViewModel extends AndroidViewModel { - - private static final String TAG = AccountViewModel.class.getSimpleName(); +public class AccountViewModel extends ViewModel { private ARepository repository; - private Database database; + private final Database database; - public AccountViewModel(@NonNull Application application) { - super(application); - - database = Database.getInstance(application); + public AccountViewModel(@NonNull Database database) { + this.database = database; } - public void setAccountType(AccountType accountType) throws Exception { - repository = ARepository.repositoryFactory(null, accountType, getApplication()); + public void setAccountType(AccountType accountType) { + repository = KoinJavaComponent.get(ARepository.class, null, + () -> DefinitionParametersKt.parametersOf(new Account(null, null, accountType))); } public void setAccount(Account account) { - try { - repository = ARepository.repositoryFactory(account, getApplication()); - } catch (Exception e) { - Log.e(TAG, e.getMessage()); - } + repository = KoinJavaComponent.get(ARepository.class, null, + () -> DefinitionParametersKt.parametersOf(account)); } public Single login(Account account, boolean insert) { @@ -71,8 +67,8 @@ public class AccountViewModel extends AndroidViewModel { return repository.getFoldersWithFeeds(); } - public Completable parseOPMLFile(Uri uri) { - return OPMLParser.read(uri, getApplication()) + public Completable parseOPMLFile(Uri uri, Context context) { + return OPMLParser.read(uri, context) .flatMapCompletable(foldersAndFeeds -> repository.insertOPMLFoldersAndFeeds(foldersAndFeeds)); } } diff --git a/app/src/main/java/com/readrops/app/viewmodels/AddFeedsViewModel.java b/app/src/main/java/com/readrops/app/viewmodels/AddFeedsViewModel.java index 303fdb0e..905fdd9d 100644 --- a/app/src/main/java/com/readrops/app/viewmodels/AddFeedsViewModel.java +++ b/app/src/main/java/com/readrops/app/viewmodels/AddFeedsViewModel.java @@ -1,14 +1,10 @@ package com.readrops.app.viewmodels; -import android.app.Application; -import android.util.Log; - import androidx.annotation.NonNull; -import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; import com.readrops.api.localfeed.LocalRSSDataSource; -import com.readrops.api.utils.HttpManager; import com.readrops.app.repositories.ARepository; import com.readrops.app.utils.FeedInsertionResult; import com.readrops.app.utils.HtmlParser; @@ -16,42 +12,36 @@ import com.readrops.app.utils.ParsingResult; import com.readrops.db.Database; import com.readrops.db.entities.account.Account; +import org.koin.core.parameter.DefinitionParametersKt; +import org.koin.java.KoinJavaComponent; + import java.util.ArrayList; import java.util.List; import io.reactivex.Single; -public class AddFeedsViewModel extends AndroidViewModel { +public class AddFeedsViewModel extends ViewModel { - private static final String TAG = AddFeedsViewModel.class.getSimpleName(); + private final Database database; + private final LocalRSSDataSource localRSSDataSource; - private ARepository repository; - private Database database; - - public AddFeedsViewModel(@NonNull Application application) { - super(application); - - database = Database.getInstance(application); + public AddFeedsViewModel(@NonNull Database database, @NonNull LocalRSSDataSource localRSSDataSource) { + this.database = database; + this.localRSSDataSource = localRSSDataSource; } public Single> addFeeds(List results, Account account) { - try { - repository = ARepository.repositoryFactory(account, getApplication()); + ARepository repository = KoinJavaComponent.get(ARepository.class, null, + () -> DefinitionParametersKt.parametersOf(account)); - return repository.addFeeds(results); - } catch (Exception e) { - Log.d(TAG, e.getMessage()); - } - - return null; + return repository.addFeeds(results); } public Single> parseUrl(String url) { return Single.create(emitter -> { - LocalRSSDataSource dataSource = new LocalRSSDataSource(HttpManager.getInstance().getOkHttpClient()); List results = new ArrayList<>(); - if (dataSource.isUrlRSSResource(url)) { + if (localRSSDataSource.isUrlRSSResource(url)) { ParsingResult parsingResult = new ParsingResult(url, null); results.add(parsingResult); } else { diff --git a/app/src/main/java/com/readrops/app/viewmodels/ItemViewModel.java b/app/src/main/java/com/readrops/app/viewmodels/ItemViewModel.java index a6243e99..a3a94a25 100644 --- a/app/src/main/java/com/readrops/app/viewmodels/ItemViewModel.java +++ b/app/src/main/java/com/readrops/app/viewmodels/ItemViewModel.java @@ -1,16 +1,15 @@ package com.readrops.app.viewmodels; -import android.app.Application; +import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; import androidx.annotation.NonNull; import androidx.core.content.FileProvider; -import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; import com.readrops.db.Database; -import com.readrops.db.dao.ItemDao; import com.readrops.db.pojo.ItemWithFeed; import java.io.File; @@ -18,22 +17,21 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -public class ItemViewModel extends AndroidViewModel { +public class ItemViewModel extends ViewModel { - private ItemDao itemDao; + private final Database database; - public ItemViewModel(@NonNull Application application) { - super(application); - itemDao = Database.getInstance(application).itemDao(); + public ItemViewModel(@NonNull Database database) { + this.database = database; } public LiveData getItemById(int id) { - return itemDao.getItemById(id); + return database.itemDao().getItemById(id); } - public Uri saveImageInCache(Bitmap bitmap) throws IOException { - File imagesFolder = new File(getApplication().getCacheDir().getAbsolutePath(), "images"); + public Uri saveImageInCache(Bitmap bitmap, Context context) throws IOException { + File imagesFolder = new File(context.getCacheDir().getAbsolutePath(), "images"); if (!imagesFolder.exists()) imagesFolder.mkdirs(); @@ -45,6 +43,6 @@ public class ItemViewModel extends AndroidViewModel { stream.flush(); stream.close(); - return FileProvider.getUriForFile(getApplication(), getApplication().getPackageName(), image); + return FileProvider.getUriForFile(context, context.getPackageName(), image); } } diff --git a/app/src/main/java/com/readrops/app/viewmodels/MainViewModel.java b/app/src/main/java/com/readrops/app/viewmodels/MainViewModel.java index 39ce3359..d2071308 100644 --- a/app/src/main/java/com/readrops/app/viewmodels/MainViewModel.java +++ b/app/src/main/java/com/readrops/app/viewmodels/MainViewModel.java @@ -1,14 +1,13 @@ package com.readrops.app.viewmodels; -import android.app.Application; - import androidx.annotation.NonNull; -import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MediatorLiveData; +import androidx.lifecycle.ViewModel; import androidx.paging.LivePagedListBuilder; import androidx.paging.PagedList; +import com.readrops.app.repositories.ARepository; import com.readrops.db.Database; import com.readrops.db.ItemsListQueryBuilder; import com.readrops.db.RoomFactoryWrapper; @@ -18,7 +17,9 @@ import com.readrops.db.entities.account.Account; import com.readrops.db.filters.FilterType; import com.readrops.db.filters.ListSortType; import com.readrops.db.pojo.ItemWithFeed; -import com.readrops.app.repositories.ARepository; + +import org.koin.core.parameter.DefinitionParametersKt; +import org.koin.java.KoinJavaComponent; import java.util.ArrayList; import java.util.Arrays; @@ -31,45 +32,40 @@ import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; -public class MainViewModel extends AndroidViewModel { +public class MainViewModel extends ViewModel { - private MediatorLiveData> itemsWithFeed; + private final MediatorLiveData> itemsWithFeed; private LiveData> lastFetch; private ARepository repository; - private Database db; + private final Database database; - private ItemsListQueryBuilder queryBuilder; + private final ItemsListQueryBuilder queryBuilder; private Account currentAccount; private List accounts; - public MainViewModel(@NonNull Application application) { - super(application); - + public MainViewModel(@NonNull Database database) { queryBuilder = new ItemsListQueryBuilder(); queryBuilder.setFilterType(FilterType.NO_FILTER); queryBuilder.setSortType(ListSortType.NEWEST_TO_OLDEST); - db = Database.getInstance(application); + this.database = database; itemsWithFeed = new MediatorLiveData<>(); } //region main query private void setRepository() { - try { - repository = ARepository.repositoryFactory(currentAccount, getApplication()); - } catch (Exception e) { - e.printStackTrace(); - } + repository = KoinJavaComponent.get(ARepository.class, null, + () -> DefinitionParametersKt.parametersOf(currentAccount)); } private void buildPagedList() { if (lastFetch != null) itemsWithFeed.removeSource(lastFetch); - lastFetch = new LivePagedListBuilder<>(new RoomFactoryWrapper<>(db.itemDao().selectAll(queryBuilder.getQuery())), + lastFetch = new LivePagedListBuilder<>(new RoomFactoryWrapper<>(database.itemDao().selectAll(queryBuilder.getQuery())), new PagedList.Config.Builder() .setPageSize(100) .setPrefetchDistance(150) @@ -77,7 +73,7 @@ public class MainViewModel extends AndroidViewModel { .build()) .build(); - itemsWithFeed.addSource(lastFetch, itemWithFeeds -> itemsWithFeed.setValue(itemWithFeeds)); + itemsWithFeed.addSource(lastFetch, itemsWithFeed::setValue); } public void invalidate() { @@ -124,7 +120,6 @@ public class MainViewModel extends AndroidViewModel { return repository.getFeedCount(currentAccount.getId()); } - @SuppressWarnings("unchecked") public Single>> getFoldersWithFeeds() { return repository.getFoldersWithFeeds(); } @@ -134,12 +129,12 @@ public class MainViewModel extends AndroidViewModel { //region Account public LiveData> getAllAccounts() { - return db.accountDao().selectAllAsync(); + return database.accountDao().selectAllAsync(); } private Completable deselectOldCurrentAccount(int accountId) { return Completable.create(emitter -> { - db.accountDao().deselectOldCurrentAccount(accountId); + database.accountDao().deselectOldCurrentAccount(accountId); emitter.onComplete(); }); } @@ -170,7 +165,7 @@ public class MainViewModel extends AndroidViewModel { // set the new account as the current one Completable setCurrentAccount = Completable.create(emitter -> { - db.accountDao().setCurrentAccount(currentAccount.getId()); + database.accountDao().setCurrentAccount(currentAccount.getId()); emitter.onComplete(); }); @@ -202,7 +197,7 @@ public class MainViewModel extends AndroidViewModel { } } - if (!currentAccountExists && accounts.size() > 0) { + if (!currentAccountExists && !accounts.isEmpty()) { setCurrentAccount(accounts.get(0)); accounts.get(0).setCurrentAccount(true); } @@ -242,7 +237,7 @@ public class MainViewModel extends AndroidViewModel { } public Completable setItemReadItLater(int itemId) { - return db.itemDao().setReadItLater(itemId); + return database.itemDao().setReadItLater(itemId); } //endregion diff --git a/app/src/main/java/com/readrops/app/viewmodels/ManageFeedsFoldersViewModel.java b/app/src/main/java/com/readrops/app/viewmodels/ManageFeedsFoldersViewModel.java index 7a357449..802006b3 100644 --- a/app/src/main/java/com/readrops/app/viewmodels/ManageFeedsFoldersViewModel.java +++ b/app/src/main/java/com/readrops/app/viewmodels/ManageFeedsFoldersViewModel.java @@ -1,48 +1,44 @@ package com.readrops.app.viewmodels; -import android.app.Application; - import androidx.annotation.NonNull; -import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; +import com.readrops.app.repositories.ARepository; import com.readrops.db.Database; import com.readrops.db.entities.Feed; import com.readrops.db.entities.Folder; import com.readrops.db.entities.account.Account; import com.readrops.db.pojo.FeedWithFolder; import com.readrops.db.pojo.FolderWithFeedCount; -import com.readrops.app.repositories.ARepository; + +import org.koin.core.parameter.DefinitionParametersKt; +import org.koin.java.KoinJavaComponent; import java.util.List; import io.reactivex.Completable; import io.reactivex.Single; -public class ManageFeedsFoldersViewModel extends AndroidViewModel { +public class ManageFeedsFoldersViewModel extends ViewModel { - private Database db; + private final Database database; private LiveData> feedsWithFolder; private LiveData> folders; private ARepository repository; private Account account; - public ManageFeedsFoldersViewModel(@NonNull Application application) { - super(application); - - db = Database.getInstance(application); + public ManageFeedsFoldersViewModel(@NonNull Database database) { + this.database = database; } private void setup() { - try { - repository = ARepository.repositoryFactory(account, getApplication()); + repository = KoinJavaComponent.get(ARepository.class, null, + () -> DefinitionParametersKt.parametersOf(account)); - feedsWithFolder = db.feedDao().getAllFeedsWithFolder(account.getId()); - folders = db.folderDao().getAllFolders(account.getId()); - } catch (Exception e) { - e.printStackTrace(); - } + feedsWithFolder = database.feedDao().getAllFeedsWithFolder(account.getId()); + folders = database.folderDao().getAllFolders(account.getId()); } public LiveData> getFeedsWithFolder() { @@ -67,7 +63,7 @@ public class ManageFeedsFoldersViewModel extends AndroidViewModel { } public LiveData> getFoldersWithFeedCount() { - return db.folderDao().getFoldersWithFeedCount(account.getId()); + return database.folderDao().getFoldersWithFeedCount(account.getId()); } public Single addFolder(Folder folder) { @@ -87,6 +83,6 @@ public class ManageFeedsFoldersViewModel extends AndroidViewModel { } public Single getFeedCountByAccount() { - return db.feedDao().getFeedCount(account.getId()); + return database.feedDao().getFeedCount(account.getId()); } } diff --git a/app/src/main/java/com/readrops/app/viewmodels/NotificationPermissionViewModel.kt b/app/src/main/java/com/readrops/app/viewmodels/NotificationPermissionViewModel.kt index b425e70e..1e2fd243 100644 --- a/app/src/main/java/com/readrops/app/viewmodels/NotificationPermissionViewModel.kt +++ b/app/src/main/java/com/readrops/app/viewmodels/NotificationPermissionViewModel.kt @@ -1,16 +1,14 @@ package com.readrops.app.viewmodels -import android.app.Application -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel import com.readrops.db.Database import com.readrops.db.entities.Feed import com.readrops.db.entities.account.Account import io.reactivex.Completable -class NotificationPermissionViewModel(application: Application) : AndroidViewModel(application) { +class NotificationPermissionViewModel(val database: Database) : ViewModel() { - val database: Database = Database.getInstance(application) var account: Account? = null fun getAccount(accountId: Int): LiveData = database.accountDao().selectAsync(accountId) diff --git a/app/src/test/java/com/readrops/app/HtmlParserTest.java b/app/src/test/java/com/readrops/app/HtmlParserTest.java deleted file mode 100644 index bf8f6e47..00000000 --- a/app/src/test/java/com/readrops/app/HtmlParserTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.readrops.app; - -import com.readrops.app.utils.HtmlParser; -import com.readrops.app.utils.ParsingResult; - -import org.junit.Assert; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -import static junit.framework.TestCase.assertEquals; - -public class HtmlParserTest { - - @Test - public void getFeedLinkTest() throws Exception { - String url = "https://github.com/readrops/Readrops"; - - ParsingResult parsingResult = new ParsingResult("https://github.com/readrops/Readrops/commits/develop.atom", "Recent Commits to Readrops:develop"); - List parsingResultList = new ArrayList<>(); - parsingResultList.add(parsingResult); - - List parsingResultList1 = HtmlParser.getFeedLink(url); - - Assert.assertEquals(parsingResultList, parsingResultList1); - } - - @Test - public void getFaviconLinkTest() { - String url = "https://github.com/readrops/Readrops"; - - assertEquals("https://github.com/fluidicon.png", HtmlParser.getFaviconLink(url)); - } -} diff --git a/app/src/test/java/com/readrops/app/HtmlParserTest.kt b/app/src/test/java/com/readrops/app/HtmlParserTest.kt new file mode 100644 index 00000000..bcff0ac2 --- /dev/null +++ b/app/src/test/java/com/readrops/app/HtmlParserTest.kt @@ -0,0 +1,40 @@ +package com.readrops.app + +import com.readrops.app.utils.HtmlParser +import com.readrops.app.utils.ParsingResult +import junit.framework.TestCase +import okhttp3.OkHttpClient +import org.junit.Assert +import org.junit.Rule +import org.junit.Test +import org.koin.dsl.module +import org.koin.test.KoinTestRule + +class HtmlParserTest { + + @get:Rule + val koinTestRule = KoinTestRule.create { + modules(module { + single { OkHttpClient() } + }) + } + + @Test + fun getFeedLinkTest() { + val url = "https://github.com/readrops/Readrops" + val parsingResult = ParsingResult("https://github.com/readrops/Readrops/commits/develop.atom", + "Recent Commits to Readrops:develop") + + val parsingResultList = mutableListOf(parsingResult) + + val parsingResultList1 = HtmlParser.getFeedLink(url) + Assert.assertEquals(parsingResultList, parsingResultList1) + } + + @Test + fun getFaviconLinkTest() { + val url = "https://github.com/readrops/Readrops" + + TestCase.assertEquals("https://github.com/fluidicon.png", HtmlParser.getFaviconLink(url)) + } +} \ No newline at end of file diff --git a/db/build.gradle b/db/build.gradle index 0d611550..435172a6 100644 --- a/db/build.gradle +++ b/db/build.gradle @@ -72,4 +72,9 @@ dependencies { api 'androidx.paging:paging-common:2.1.2' api 'joda-time:joda-time:2.10.5' + + def koin_version = "2.1.6" + api "org.koin:koin-android:$koin_version" + api "org.koin:koin-androidx-scope:$koin_version" + api "org.koin:koin-androidx-viewmodel:$koin_version" } diff --git a/db/src/main/java/com/readrops/db/Database.java b/db/src/main/java/com/readrops/db/Database.java index f9cd69f8..3d23536a 100644 --- a/db/src/main/java/com/readrops/db/Database.java +++ b/db/src/main/java/com/readrops/db/Database.java @@ -1,9 +1,6 @@ package com.readrops.db; -import android.content.Context; - import androidx.annotation.NonNull; -import androidx.room.Room; import androidx.room.RoomDatabase; import androidx.room.TypeConverters; import androidx.room.migration.Migration; @@ -30,18 +27,7 @@ public abstract class Database extends RoomDatabase { public abstract FolderDao folderDao(); public abstract AccountDao accountDao(); - - private static Database database; - - public static Database getInstance(Context context) { - if (database == null) - database = Room.databaseBuilder(context, Database.class, "readrops-db") - .addMigrations(MIGRATION_1_2) - .build(); - - return database; - } - + public static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(@NonNull SupportSQLiteDatabase database) { diff --git a/db/src/main/java/com/readrops/db/DbModule.kt b/db/src/main/java/com/readrops/db/DbModule.kt new file mode 100644 index 00000000..031f9361 --- /dev/null +++ b/db/src/main/java/com/readrops/db/DbModule.kt @@ -0,0 +1,13 @@ +package com.readrops.db + +import androidx.room.Room +import org.koin.dsl.module + +val dbModule = module { + + single(createdAtStart = true) { + Room.databaseBuilder(get(), Database::class.java, "readrops-db") + .addMigrations(Database.MIGRATION_1_2) + .build() + } +} \ No newline at end of file