Add nextcloud news feed insertion and account choice in the add feed activity

This commit is contained in:
Shinokuni 2019-06-23 13:33:10 +02:00
parent b0b8e4202a
commit e12cf1ec7f
11 changed files with 214 additions and 40 deletions

View File

@ -7,8 +7,10 @@ import android.util.Patterns;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
@ -27,8 +29,10 @@ import com.readrops.app.database.entities.Account;
import com.readrops.app.database.entities.Feed;
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.app.viewmodels.AddFeedsViewModel;
import com.readrops.app.views.AccountArrayAdapter;
import java.util.ArrayList;
import java.util.List;
@ -49,6 +53,9 @@ public class AddFeedActivity extends AppCompatActivity implements View.OnClickLi
private ProgressBar feedInsertionProgressBar;
private RecyclerView insertionResultsRecyclerView;
private Spinner accountSpinner;
private AccountArrayAdapter arrayAdapter;
private ItemAdapter<ParsingResult> parseItemsAdapter;
private ItemAdapter<FeedInsertionResult> insertionResultsAdapter;
@ -69,6 +76,7 @@ public class AddFeedActivity extends AppCompatActivity implements View.OnClickLi
resultsTextView = findViewById(R.id.add_feed_results_text_view);
feedInsertionProgressBar = findViewById(R.id.add_feed_insert_progressbar);
insertionResultsRecyclerView = findViewById(R.id.add_feed_inserted_results_recyclerview);
accountSpinner = findViewById(R.id.add_feed_account_spinner);
load.setOnClickListener(this);
validate.setOnClickListener(this);
@ -145,6 +153,25 @@ public class AddFeedActivity extends AppCompatActivity implements View.OnClickLi
insertionResultsRecyclerView.setAdapter(FastAdapter.with(insertionResultsAdapter));
insertionResultsRecyclerView.setLayoutManager(layoutManager1);
viewModel.getAccounts().observe(this, accounts -> {
arrayAdapter = new AccountArrayAdapter(this, accounts);
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
accountSpinner.setAdapter(arrayAdapter);
accountSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
});
feedsToUpdate = new ArrayList<>();
}
@ -233,8 +260,12 @@ public class AddFeedActivity extends AppCompatActivity implements View.OnClickLi
feedsToInsert.add(result);
}
// TODO : choose the right account
viewModel.addFeeds(feedsToInsert, new Account())
Account account = (Account) accountSpinner.getSelectedItem();
account.setLogin(SharedPreferencesManager.readString(this, account.getLoginKey()));
account.setPassword(SharedPreferencesManager.readString(this, account.getPasswordKey()));
viewModel.addFeeds(feedsToInsert, account)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableSingleObserver<List<FeedInsertionResult>>() {
@ -245,7 +276,7 @@ public class AddFeedActivity extends AppCompatActivity implements View.OnClickLi
@Override
public void onError(Throwable e) {
Toast.makeText(AddFeedActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}

View File

@ -45,9 +45,6 @@ public abstract class Database extends RoomDatabase {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
Folder folder = new Folder("reserved");
//new Thread(() -> database.folderDao().insert(folder)).start();
}
@Override

View File

@ -20,8 +20,8 @@ import org.jsoup.Jsoup;
@Entity(foreignKeys = {@ForeignKey(entity = Folder.class, parentColumns = "id",
childColumns = "folder_id", onDelete = ForeignKey.SET_NULL),
@ForeignKey(entity = Account.class, parentColumns = "id", childColumns = "account_id",
onDelete = ForeignKey.CASCADE)})
@ForeignKey(entity = Account.class, parentColumns = "id", childColumns = "account_id",
onDelete = ForeignKey.CASCADE)})
public class Feed implements Parcelable {
@PrimaryKey(autoGenerate = true)
@ -38,10 +38,12 @@ public class Feed implements Parcelable {
private String lastUpdated;
@ColumnInfo(name = "text_color")
private @ColorInt int textColor;
private @ColorInt
int textColor;
@ColumnInfo(name = "background_color")
private @ColorInt int backgroundColor;
private @ColorInt
int backgroundColor;
@ColumnInfo(name = "icon_url")
private String iconUrl;
@ -52,7 +54,7 @@ public class Feed implements Parcelable {
private String lastModified;
@ColumnInfo(name = "folder_id", index = true)
private int folderId;
private Integer folderId; // nullable foreign key so Integer instead of int
private int remoteId;
@ -149,7 +151,8 @@ public class Feed implements Parcelable {
this.lastUpdated = lastUpdated;
}
public @ColorInt int getTextColor() {
public @ColorInt
int getTextColor() {
return textColor;
}
@ -157,7 +160,8 @@ public class Feed implements Parcelable {
this.textColor = textColor;
}
public @ColorInt int getBackgroundColor() {
public @ColorInt
int getBackgroundColor() {
return backgroundColor;
}
@ -189,11 +193,11 @@ public class Feed implements Parcelable {
this.lastModified = lastModified;
}
public int getFolderId() {
public Integer getFolderId() {
return folderId;
}
public void setFolderId(int folderId) {
public void setFolderId(Integer folderId) {
this.folderId = folderId;
}
@ -234,9 +238,7 @@ public class Feed implements Parcelable {
feed.setEtag(rssFeed.getEtag());
feed.setLastModified(rssFeed.getLastModified());
// as sqlite doesn't support null foreign keys, a default folder is linked to the feed
// This default folder was inserted at room db creation (see Database.java)
feed.setFolderId(1);
feed.setFolderId(null);
return feed;
}
@ -254,7 +256,7 @@ public class Feed implements Parcelable {
feed.setEtag(atomFeed.getEtag());
feed.setLastModified(atomFeed.getLastModified());
feed.setFolderId(1);
feed.setFolderId(null);
return feed;
}
@ -266,13 +268,12 @@ public class Feed implements Parcelable {
feed.setUrl(jsonFeed.getFeedUrl());
feed.setSiteUrl(jsonFeed.getHomePageUrl());
feed.setDescription(jsonFeed.getDescription());
//feed.setLastUpdated(jsonFeed.); maybe later ?
feed.setEtag(jsonFeed.getEtag());
feed.setLastModified(jsonFeed.getLastModified());
feed.setIconUrl(jsonFeed.getFaviconUrl());
feed.setFolderId(1);
feed.setFolderId(null);
return feed;
}

View File

@ -1,6 +1,7 @@
package com.readrops.app.repositories;
import android.app.Application;
import android.database.sqlite.SQLiteConstraintException;
import android.util.TimingLogger;
import com.readrops.app.database.entities.Account;
@ -18,9 +19,11 @@ import com.readrops.readropslibrary.services.nextcloudnews.NextNewsAPI;
import com.readrops.readropslibrary.services.nextcloudnews.SyncData;
import com.readrops.readropslibrary.services.nextcloudnews.SyncResult;
import com.readrops.readropslibrary.services.nextcloudnews.json.NextNewsFeed;
import com.readrops.readropslibrary.services.nextcloudnews.json.NextNewsFeeds;
import com.readrops.readropslibrary.services.nextcloudnews.json.NextNewsFolder;
import com.readrops.readropslibrary.services.nextcloudnews.json.NextNewsItem;
import com.readrops.readropslibrary.services.nextcloudnews.json.NextNewsUser;
import com.readrops.readropslibrary.utils.UnknownFormatException;
import org.joda.time.LocalDateTime;
@ -116,7 +119,41 @@ public class NextNewsRepository extends ARepository {
@Override
public Single<List<FeedInsertionResult>> addFeeds(List<ParsingResult> results, Account account) {
return null;
return Single.create(emitter -> {
List<FeedInsertionResult> feedInsertionResults = new ArrayList<>();
NextNewsAPI newsAPI = new NextNewsAPI();
for (ParsingResult result : results) {
FeedInsertionResult insertionResult = new FeedInsertionResult();
try {
Credentials credentials = new Credentials(account.getLogin(), account.getPassword(), account.getUrl());
NextNewsFeeds nextNewsFeeds = newsAPI.createFeed(credentials, result.getUrl(), 0);
if (nextNewsFeeds != null) {
List<Feed> newFeeds = insertFeeds(nextNewsFeeds.getFeeds(), account);
// there is always only one object in the list, see nextcloud news api doc
insertionResult.setFeed(newFeeds.get(0));
} else
insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.UNKNOWN_ERROR);
} catch (Exception e) {
if (e instanceof IOException)
insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.NETWORK_ERROR);
else if (e instanceof UnknownFormatException)
insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.FORMAT_ERROR);
else if (e instanceof SQLiteConstraintException)
insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.DB_ERROR);
else
insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.UNKNOWN_ERROR);
}
feedInsertionResults.add(insertionResult);
}
emitter.onSuccess(feedInsertionResults);
});
}
@Override
@ -134,7 +171,7 @@ public class NextNewsRepository extends ARepository {
return null;
}
private void insertFeeds(List<NextNewsFeed> feeds, Account account) {
private List<Feed> insertFeeds(List<NextNewsFeed> feeds, Account account) {
List<Feed> newFeeds = new ArrayList<>();
for (NextNewsFeed nextNewsFeed : feeds) {
@ -142,14 +179,14 @@ public class NextNewsRepository extends ARepository {
if (!database.feedDao().remoteFeedExists(nextNewsFeed.getId(), account.getId())) {
Feed feed = FeedMatcher.nextNewsFeedToFeed(nextNewsFeed, account);
// if the Nextcloud feed has a folder, it is already inserted, so we have to get its local id
if (nextNewsFeed.getFolderId() != 0) {
int folderId = database.folderDao().getRemoteFolderLocalId(nextNewsFeed.getFolderId(), account.getId());
if (folderId != 0)
feed.setFolderId(folderId);
}
} else
feed.setFolderId(null);
newFeeds.add(feed);
}
@ -168,6 +205,8 @@ public class NextNewsRepository extends ARepository {
.doOnNext(feed1 -> database.feedDao().updateColors(feed1.getId(),
feed1.getTextColor(), feed1.getBackgroundColor()))
.subscribe();
return insertedFeeds;
}
private void insertFolders(List<NextNewsFolder> folders, Account account) {

View File

@ -1,12 +1,12 @@
package com.readrops.app.utils;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import com.mikepenz.fastadapter.FastAdapter;
import com.mikepenz.fastadapter.items.AbstractItem;
import com.readrops.app.R;

View File

@ -3,9 +3,13 @@ package com.readrops.app.viewmodels;
import android.app.Application;
import androidx.lifecycle.AndroidViewModel;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import com.readrops.app.database.Database;
import com.readrops.app.database.entities.Account;
import com.readrops.app.repositories.ARepository;
import com.readrops.app.repositories.LocalFeedRepository;
import com.readrops.app.repositories.NextNewsRepository;
import com.readrops.app.utils.FeedInsertionResult;
import com.readrops.app.utils.HtmlParser;
import com.readrops.app.utils.ParsingResult;
@ -18,15 +22,25 @@ import io.reactivex.Single;
public class AddFeedsViewModel extends AndroidViewModel {
private LocalFeedRepository repository;
private ARepository repository;
private Database database;
public AddFeedsViewModel(@NonNull Application application) {
super(application);
repository = new LocalFeedRepository(application);
database = Database.getInstance(application);
}
public Single<List<FeedInsertionResult>> addFeeds(List<ParsingResult> results, Account account) {
switch (account.getAccountType()) {
case LOCAL:
repository = new LocalFeedRepository(getApplication());
break;
case NEXTCLOUD_NEWS:
repository = new NextNewsRepository(getApplication());
break;
}
return repository.addFeeds(results, account);
}
@ -45,4 +59,8 @@ public class AddFeedsViewModel extends AndroidViewModel {
emitter.onSuccess(results);
});
}
public LiveData<List<Account>> getAccounts() {
return database.accountDao().selectAll();
}
}

View File

@ -0,0 +1,54 @@
package com.readrops.app.views;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.readrops.app.R;
import com.readrops.app.database.entities.Account;
import java.util.List;
public class AccountArrayAdapter extends ArrayAdapter<Account> {
public AccountArrayAdapter(@NonNull Context context, @NonNull List<Account> objects) {
super(context, 0, objects);
}
@Override
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
return createItemView(position, convertView, parent);
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
return createItemView(position, convertView, parent);
}
private View createItemView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.account_type_item, parent, false);
}
Account account = getItem(position);
ImageView accountIcon = convertView.findViewById(R.id.account_type_logo);
TextView accountName = convertView.findViewById(R.id.account_type_name);
accountIcon.setImageResource(account.getAccountType().getIconRes());
accountName.setText(account.getAccountType().getName());
return convertView;
}
}

View File

@ -33,8 +33,8 @@
android:id="@+id/add_feed_text_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/feed_url"
android:drawableEnd="@drawable/ic_cancel_grey"/>
android:drawableEnd="@drawable/ic_cancel_grey"
android:hint="@string/feed_url" />
</com.google.android.material.textfield.TextInputLayout>
@ -83,17 +83,26 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<Spinner
android:id="@+id/add_feed_account_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/add_feed_main_layout" />
<Button
android:id="@+id/add_feed_ok"
style="@style/GenericButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:enabled="true"
android:text="@string/validate"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.6"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/add_feed_main_layout" />
app:layout_constraintTop_toBottomOf="@id/add_feed_account_spinner" />
<ProgressBar
android:id="@+id/add_feed_insert_progressbar"

View File

@ -10,6 +10,7 @@ import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
@ -35,4 +36,7 @@ public interface NextNewsService {
@PUT("items/{stateType}/multiple")
Call<ResponseBody> setArticlesState(@Path("stateType") String stateType, @Body NextNewsItemIds items);
@POST("feeds")
Call<NextNewsFeeds> createFeed(@Query("url") String url, @Query("folderId") int folderId);
}

View File

@ -10,6 +10,8 @@ import com.readrops.readropslibrary.services.nextcloudnews.json.NextNewsItemIds;
import com.readrops.readropslibrary.services.nextcloudnews.json.NextNewsItems;
import com.readrops.readropslibrary.services.nextcloudnews.json.NextNewsUser;
import com.readrops.readropslibrary.utils.HttpManager;
import com.readrops.readropslibrary.utils.LibUtils;
import com.readrops.readropslibrary.utils.UnknownFormatException;
import java.io.IOException;
@ -35,11 +37,15 @@ public class NextNewsAPI {
.build();
}
public NextNewsUser login(Credentials credentials) throws IOException {
private NextNewsService createAPI(@NonNull Credentials credentials) {
HttpManager httpManager = new HttpManager(credentials);
Retrofit retrofit = getConfiguredRetrofitInstance(httpManager);
api = retrofit.create(NextNewsService.class);
return retrofit.create(NextNewsService.class);
}
public NextNewsUser login(Credentials credentials) throws IOException {
api = createAPI(credentials);
Response<NextNewsUser> response = api.getUser().execute();
@ -49,11 +55,24 @@ public class NextNewsAPI {
return response.body();
}
public SyncResult sync(@NonNull Credentials credentials, @NonNull SyncType syncType, @Nullable SyncData data) throws IOException {
HttpManager httpManager = new HttpManager(credentials);
Retrofit retrofit = getConfiguredRetrofitInstance(httpManager);
public @Nullable NextNewsFeeds createFeed(Credentials credentials, String url, int folderId)
throws IOException, UnknownFormatException {
api = createAPI(credentials);
api = retrofit.create(NextNewsService.class);
Response<NextNewsFeeds> response = api.createFeed(url, folderId).execute();
if (!response.isSuccessful()) {
if (response.code() == LibUtils.UNPROCESSABLE_CODE)
throw new UnknownFormatException();
else
return null;
}
return response.body();
}
public SyncResult sync(@NonNull Credentials credentials, @NonNull SyncType syncType, @Nullable SyncData data) throws IOException {
api = createAPI(credentials);
SyncResult syncResult = new SyncResult();
switch (syncType) {

View File

@ -18,6 +18,8 @@ public final class LibUtils {
public static final String LAST_MODIFIED_HEADER = "Last-Modified";
public static final String IF_MODIFIED_HEADER = "If-Modified-Since";
public static final int UNPROCESSABLE_CODE = 422;
public static String inputStreamToString(InputStream input) {
Scanner scanner = new Scanner(input).useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";