Preparing to support multi user. Use the database to store accounts' info. LoginActivity is successfully refactored. Any other features are unavailable for now.

This commit is contained in:
Alex Ning 2019-08-07 10:54:47 +08:00
parent 77d83654aa
commit 7f2bc01180
10 changed files with 357 additions and 20 deletions

View File

@ -0,0 +1,15 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="FieldCanBeLocal" enabled="true" level="WARNING" enabled_by_default="true">
<option name="EXCLUDE_ANNOS">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="androidx.room.ColumnInfo" />
</list>
</value>
</option>
<option name="IGNORE_FIELDS_USED_IN_MULTIPLE_METHODS" value="true" />
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,72 @@
package Account;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "accounts")
public class Account {
@PrimaryKey
@NonNull
@ColumnInfo(name = "username")
private String username;
@ColumnInfo(name = "profile_image_url")
private String profileImageUrl;
@ColumnInfo(name = "banner_image_url")
private String bannerImageUrl;
@ColumnInfo(name = "karma")
private int karma;
@ColumnInfo(name = "access_token")
private String accessToken;
@ColumnInfo(name = "refresh_token")
private String refreshToken;
@ColumnInfo(name = "code")
private String code;
@ColumnInfo(name = "is_current_user")
private boolean isCurrentUser;
public Account(@NonNull String username, String accessToken, String refreshToken, String code,
String profileImageUrl, String bannerImageUrl, int karma, boolean isCurrentUser) {
this.username = username;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.code = code;
this.profileImageUrl = profileImageUrl;
this.bannerImageUrl = bannerImageUrl;
this.karma = karma;
this.isCurrentUser = isCurrentUser;
}
public String getUsername() {
return username;
}
public String getProfileImageUrl() {
return profileImageUrl;
}
public String getBannerImageUrl() {
return bannerImageUrl;
}
public int getKarma() {
return karma;
}
public String getAccessToken() {
return accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
public String getCode() {
return code;
}
public boolean isCurrentUser() {
return isCurrentUser;
}
}

View File

@ -0,0 +1,22 @@
package Account;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
@Dao
public interface AccountDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Account account);
@Query("DELETE FROM accounts")
void deleteAllAccounts();
@Query("SELECT * FROM accounts WHERE username = :userName COLLATE NOCASE LIMIT 1")
LiveData<Account> getAccountLiveData(String userName);
@Query("SELECT * FROM accounts WHERE username = :userName COLLATE NOCASE LIMIT 1")
Account getAccountData(String userName);
}

View File

@ -0,0 +1,40 @@
package Account;
import android.app.Application;
import android.os.AsyncTask;
import androidx.lifecycle.LiveData;
public class AccountRepository {
private AccountDao mAccountDao;
private LiveData<Account> mAccountLiveData;
AccountRepository(Application application, String username) {
mAccountDao = AccountRoomDatabase.getDatabase(application).accountDao();
mAccountLiveData = mAccountDao.getAccountLiveData(username);
}
LiveData<Account> getAccountLiveData() {
return mAccountLiveData;
}
public void insert(Account Account) {
new InsertAsyncTask(mAccountDao).execute(Account);
}
private static class InsertAsyncTask extends AsyncTask<Account, Void, Void> {
private AccountDao mAsyncTaskDao;
InsertAsyncTask(AccountDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(final Account... params) {
mAsyncTaskDao.insert(params[0]);
return null;
}
}
}

View File

@ -0,0 +1,27 @@
package Account;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
@Database(entities = {Account.class}, version = 1)
public abstract class AccountRoomDatabase extends RoomDatabase {
private static AccountRoomDatabase INSTANCE;
public abstract AccountDao accountDao();
public static AccountRoomDatabase getDatabase(final Context context) {
if(INSTANCE == null) {
synchronized (AccountRoomDatabase.class) {
if(INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AccountRoomDatabase.class, "accounts")
.build();
}
}
}
return INSTANCE;
}
}

View File

@ -0,0 +1,47 @@
package Account;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
public class AccountViewModel extends AndroidViewModel {
private AccountRepository mAccountRepository;
private LiveData<Account> mAccountLiveData;
public AccountViewModel(Application application, String id) {
super(application);
mAccountRepository = new AccountRepository(application, id);
mAccountLiveData = mAccountRepository.getAccountLiveData();
}
public LiveData<Account> getAccountLiveData() {
return mAccountLiveData;
}
public void insert(Account userData) {
mAccountRepository.insert(userData);
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
@NonNull
private final Application mApplication;
private final String userName;
public Factory(@NonNull Application application, String userName) {
mApplication = application;
this.userName = userName;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
//noinspection unchecked
return (T) new AccountViewModel(mApplication, userName);
}
}
}

View File

@ -1,9 +1,10 @@
package ml.docilealligator.infinityforreddit;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import android.util.Log;
import androidx.annotation.NonNull;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Retrofit;
@ -39,4 +40,28 @@ class FetchMyInfo {
}
});
}
static void fetchMyInfo(final Retrofit retrofit, String accessToken,
final FetchUserMyListener fetchUserMyListener) {
RedditAPI api = retrofit.create(RedditAPI.class);
Call<String> userInfo = api.getMyInfo(RedditUtils.getOAuthHeader(accessToken));
userInfo.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull Call<String> call, @NonNull retrofit2.Response<String> response) {
if(response.isSuccessful()) {
fetchUserMyListener.onFetchMyInfoSuccess(response.body());
} else {
Log.i("call failed", response.message());
fetchUserMyListener.onFetchMyInfoFail();
}
}
@Override
public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
Log.i("call failed", t.getMessage());
fetchUserMyListener.onFetchMyInfoFail();
}
});
}
}

View File

@ -12,6 +12,9 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONException;
import org.json.JSONObject;
@ -21,8 +24,7 @@ import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import Account.AccountRoomDatabase;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
@ -36,6 +38,10 @@ public class LoginActivity extends AppCompatActivity {
@Named("no_oauth")
Retrofit mRetrofit;
@Inject
@Named("oauth")
Retrofit mOauthRetrofit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -85,41 +91,69 @@ public class LoginActivity extends AppCompatActivity {
public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {
if(response.isSuccessful()) {
try {
JSONObject responseJSON = new JSONObject(response.body());
String accountResponse = response.body();
if(accountResponse == null) {
//Handle error
return;
}
JSONObject responseJSON = new JSONObject(accountResponse);
String accessToken = responseJSON.getString(RedditUtils.ACCESS_TOKEN_KEY);
String refreshToken = responseJSON.getString(RedditUtils.REFRESH_TOKEN_KEY);
editor.putString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, accessToken);
editor.putString(SharedPreferencesUtils.REFRESH_TOKEN_KEY, refreshToken);
editor.apply();
FetchMyInfo.fetchMyInfo(mOauthRetrofit, accessToken, new FetchMyInfo.FetchUserMyListener() {
@Override
public void onFetchMyInfoSuccess(String response) {
ParseMyInfo.parseMyInfo(response, new ParseMyInfo.ParseMyInfoListener() {
@Override
public void onParseMyInfoSuccess(String name, String profileImageUrl, String bannerImageUrl, int karma) {
new ParseAndInsertAccount(name, accessToken, refreshToken, profileImageUrl, bannerImageUrl,
karma, authCode, AccountRoomDatabase.getDatabase(LoginActivity.this).accountDao(),
() -> {
Intent resultIntent = new Intent();
setResult(Activity.RESULT_OK, resultIntent);
finish();
}).execute();
}
Intent resultIntent = new Intent();
setResult(Activity.RESULT_OK, resultIntent);
finish();
@Override
public void onParseMyInfoFail() {
Toast.makeText(LoginActivity.this, R.string.parse_user_info_error, Toast.LENGTH_SHORT).show();
finish();
}
});
}
@Override
public void onFetchMyInfoFail() {
Toast.makeText(LoginActivity.this, R.string.cannot_fetch_user_info, Toast.LENGTH_SHORT).show();
finish();
}
});
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(LoginActivity.this, "Error occurred when parsing the JSON response", Toast.LENGTH_SHORT).show();
Toast.makeText(LoginActivity.this, R.string.parse_json_response_error, Toast.LENGTH_SHORT).show();
finish();
}
} else {
Toast.makeText(LoginActivity.this, "Error Retrieving the token", Toast.LENGTH_SHORT).show();
Toast.makeText(LoginActivity.this, R.string.retrieve_token_error, Toast.LENGTH_SHORT).show();
finish();
}
}
@Override
public void onFailure(Call<String> call, Throwable t) {
Toast.makeText(LoginActivity.this, "Error Retrieving the token", Toast.LENGTH_SHORT).show();
public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
Toast.makeText(LoginActivity.this, R.string.retrieve_token_error, Toast.LENGTH_SHORT).show();
finish();
}
});
} else {
Toast.makeText(LoginActivity.this, "Something went wrong. Try again later.", Toast.LENGTH_SHORT).show();
Toast.makeText(LoginActivity.this, R.string.something_went_wrong, Toast.LENGTH_SHORT).show();
finish();
}
} else if (url.contains("error=access_denied")) {
Toast.makeText(LoginActivity.this, "Access denied", Toast.LENGTH_SHORT).show();
Toast.makeText(LoginActivity.this, R.string.access_denied, Toast.LENGTH_SHORT).show();
finish();
} else {
view.loadUrl(url);
@ -142,10 +176,9 @@ public class LoginActivity extends AppCompatActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return false;
}

View File

@ -0,0 +1,50 @@
package ml.docilealligator.infinityforreddit;
import android.os.AsyncTask;
import Account.Account;
import Account.AccountDao;
class ParseAndInsertAccount extends AsyncTask<Void, Void, Void> {
interface ParseAndInsertAccountListener {
void success();
}
private String username;
private String accessToken;
private String refreshToken;
private String profileImageUrl;
private String bannerImageUrl;
private int karma;
private String code;
private AccountDao accountDao;
private ParseAndInsertAccountListener parseAndInsertAccountListener;
ParseAndInsertAccount(String username, String accessToken, String refreshToken, String profileImageUrl, String bannerImageUrl,
int karma, String code, AccountDao accountDao,
ParseAndInsertAccountListener parseAndInsertAccountListener) {
this.username = username;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.profileImageUrl = profileImageUrl;
this.bannerImageUrl = bannerImageUrl;
this.karma = karma;
this.code = code;
this.accountDao = accountDao;
this.parseAndInsertAccountListener = parseAndInsertAccountListener;
}
@Override
protected Void doInBackground(Void... voids) {
Account account = new Account(username, accessToken, refreshToken, code, profileImageUrl,
bannerImageUrl, karma, true);
accountDao.insert(account);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
parseAndInsertAccountListener.success();
}
}

View File

@ -25,6 +25,12 @@
<string name="action_send">Send</string>
<string name="action_sort">Sort</string>
<string name="parse_json_response_error">Error occurred when parsing the JSON response</string>
<string name="retrieve_token_error">Error Retrieving the token</string>
<string name="something_went_wrong">Something went wrong. Try again later.</string>
<string name="access_denied">Access denied</string>
<string name="parse_user_info_error">Error occurred when parsing the user info</string>
<string name="tap_to_retry">Error loading image. Tap to retry.</string>
<string name="load_posts_error">Error loading posts.\nTap to retry.</string>
<string name="search_subreddits_error">Error searching subreddits.\nTap to retry.</string>