From e11841f19d9bd31be82b87053128668c0518143e Mon Sep 17 00:00:00 2001 From: wchino Date: Sun, 5 Apr 2026 22:29:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=95=B0=E6=8D=AE=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=92=8C=E7=BD=91=E7=BB=9C=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../smsmessage/model/ApiError.java | 53 +++ .../model/AppNotificationSettings.java | 21 + .../smsmessage/model/BankInfo.java | 94 +++++ .../smsmessage/model/BankListResponse.java | 23 ++ .../smsmessage/model/LoginResponse.java | 37 ++ .../model/UploadNotificationResponse.java | 75 ++++ .../smsmessage/model/UserInfo.java | 76 ++++ .../smsmessage/network/ApiService.java | 376 ++++++++++++++++++ .../smsmessage/network/TokenManager.java | 84 ++++ 9 files changed, 839 insertions(+) create mode 100644 app/src/main/java/com/miraclegarden/smsmessage/model/ApiError.java create mode 100644 app/src/main/java/com/miraclegarden/smsmessage/model/AppNotificationSettings.java create mode 100644 app/src/main/java/com/miraclegarden/smsmessage/model/BankInfo.java create mode 100644 app/src/main/java/com/miraclegarden/smsmessage/model/BankListResponse.java create mode 100644 app/src/main/java/com/miraclegarden/smsmessage/model/LoginResponse.java create mode 100644 app/src/main/java/com/miraclegarden/smsmessage/model/UploadNotificationResponse.java create mode 100644 app/src/main/java/com/miraclegarden/smsmessage/model/UserInfo.java create mode 100644 app/src/main/java/com/miraclegarden/smsmessage/network/ApiService.java create mode 100644 app/src/main/java/com/miraclegarden/smsmessage/network/TokenManager.java diff --git a/app/src/main/java/com/miraclegarden/smsmessage/model/ApiError.java b/app/src/main/java/com/miraclegarden/smsmessage/model/ApiError.java new file mode 100644 index 0000000..62b2a6b --- /dev/null +++ b/app/src/main/java/com/miraclegarden/smsmessage/model/ApiError.java @@ -0,0 +1,53 @@ +package com.miraclegarden.smsmessage.model; + +/** + * ********************** + * + * @Author MiracleGarden + * 创建时间: 2026/4/5 + * API错误响应 + * ********************** + */ +public class ApiError { + private int statusCode; + private String error; + private String message; + + public ApiError(int statusCode, String error, String message) { + this.statusCode = statusCode; + this.error = error; + this.message = message; + } + + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + /** + * 判断是否因token失效导致的错误 + */ + public boolean isTokenExpired() { + return statusCode == 401 && message != null && + (message.contains("登录已失效") || message.contains("token") || message.contains("Token")); + } +} diff --git a/app/src/main/java/com/miraclegarden/smsmessage/model/AppNotificationSettings.java b/app/src/main/java/com/miraclegarden/smsmessage/model/AppNotificationSettings.java new file mode 100644 index 0000000..4dfe043 --- /dev/null +++ b/app/src/main/java/com/miraclegarden/smsmessage/model/AppNotificationSettings.java @@ -0,0 +1,21 @@ +package com.miraclegarden.smsmessage.model; + +/** + * ********************** + * + * @Author MiracleGarden + * 创建时间: 2026/4/5 + * App通知设置响应 + * ********************** + */ +public class AppNotificationSettings { + private boolean appNotificationAutoProcessEnabled; + + public boolean isAppNotificationAutoProcessEnabled() { + return appNotificationAutoProcessEnabled; + } + + public void setAppNotificationAutoProcessEnabled(boolean enabled) { + this.appNotificationAutoProcessEnabled = enabled; + } +} diff --git a/app/src/main/java/com/miraclegarden/smsmessage/model/BankInfo.java b/app/src/main/java/com/miraclegarden/smsmessage/model/BankInfo.java new file mode 100644 index 0000000..e02a42c --- /dev/null +++ b/app/src/main/java/com/miraclegarden/smsmessage/model/BankInfo.java @@ -0,0 +1,94 @@ +package com.miraclegarden.smsmessage.model; + +/** + * ********************** + * + * @Author MiracleGarden + * 创建时间: 2026/4/5 + * 银行账户信息数据模型 + * ********************** + */ +public class BankInfo { + private String id; // 银行ID (bankInfoId) + private String bankName; // 银行名称 + private String account; // 账户 + private String remark; // 备注 + private String userId; // 用户ID + private String createdAt; // 创建时间 + private String updatedAt; // 更新时间 + + public BankInfo() { + } + + public BankInfo(String bankName, String account, String remark) { + this.bankName = bankName; + this.account = account; + this.remark = remark; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBankName() { + return bankName; + } + + public void setBankName(String bankName) { + this.bankName = bankName; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public String getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(String updatedAt) { + this.updatedAt = updatedAt; + } + + /** + * 获取显示名称:银行名 + 账户 + */ + public String getDisplayName() { + if (account != null && !account.isEmpty()) { + return bankName + " (" + account + ")"; + } + return bankName; + } +} diff --git a/app/src/main/java/com/miraclegarden/smsmessage/model/BankListResponse.java b/app/src/main/java/com/miraclegarden/smsmessage/model/BankListResponse.java new file mode 100644 index 0000000..27b11eb --- /dev/null +++ b/app/src/main/java/com/miraclegarden/smsmessage/model/BankListResponse.java @@ -0,0 +1,23 @@ +package com.miraclegarden.smsmessage.model; + +import java.util.List; + +/** + * ********************** + * + * @Author MiracleGarden + * 创建时间: 2026/4/5 + * 银行账户列表响应 + * ********************** + */ +public class BankListResponse { + private List banks; + + public List getBanks() { + return banks; + } + + public void setBanks(List banks) { + this.banks = banks; + } +} diff --git a/app/src/main/java/com/miraclegarden/smsmessage/model/LoginResponse.java b/app/src/main/java/com/miraclegarden/smsmessage/model/LoginResponse.java new file mode 100644 index 0000000..807b66d --- /dev/null +++ b/app/src/main/java/com/miraclegarden/smsmessage/model/LoginResponse.java @@ -0,0 +1,37 @@ +package com.miraclegarden.smsmessage.model; + +/** + * ********************** + * + * @Author MiracleGarden + * 创建时间: 2026/4/5 + * 登录响应数据模型 + * ********************** + */ +public class LoginResponse { + private String token; // JWT token + private UserInfo user; // 用户信息 + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public UserInfo getUser() { + return user; + } + + public void setUser(UserInfo user) { + this.user = user; + } + + /** + * 判断是否为首次登录 + */ + public boolean isFirstLogin() { + return user != null && user.isFirstLogin(); + } +} diff --git a/app/src/main/java/com/miraclegarden/smsmessage/model/UploadNotificationResponse.java b/app/src/main/java/com/miraclegarden/smsmessage/model/UploadNotificationResponse.java new file mode 100644 index 0000000..ff4bb11 --- /dev/null +++ b/app/src/main/java/com/miraclegarden/smsmessage/model/UploadNotificationResponse.java @@ -0,0 +1,75 @@ +package com.miraclegarden.smsmessage.model; + +/** + * ********************** + * + * @Author MiracleGarden + * 创建时间: 2026/4/5 + * 通知上传响应 + * ********************** + */ +public class UploadNotificationResponse { + private boolean ok; + private int received; + private String id; + private String processingStatus; + private String emailSyncRecordId; + private String duplicateOfEmailSyncRecordId; + private String errorMessage; + + public boolean isOk() { + return ok; + } + + public void setOk(boolean ok) { + this.ok = ok; + } + + public int getReceived() { + return received; + } + + public void setReceived(int received) { + this.received = received; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getProcessingStatus() { + return processingStatus; + } + + public void setProcessingStatus(String processingStatus) { + this.processingStatus = processingStatus; + } + + public String getEmailSyncRecordId() { + return emailSyncRecordId; + } + + public void setEmailSyncRecordId(String emailSyncRecordId) { + this.emailSyncRecordId = emailSyncRecordId; + } + + public String getDuplicateOfEmailSyncRecordId() { + return duplicateOfEmailSyncRecordId; + } + + public void setDuplicateOfEmailSyncRecordId(String duplicateOfEmailSyncRecordId) { + this.duplicateOfEmailSyncRecordId = duplicateOfEmailSyncRecordId; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/app/src/main/java/com/miraclegarden/smsmessage/model/UserInfo.java b/app/src/main/java/com/miraclegarden/smsmessage/model/UserInfo.java new file mode 100644 index 0000000..9e7c1e9 --- /dev/null +++ b/app/src/main/java/com/miraclegarden/smsmessage/model/UserInfo.java @@ -0,0 +1,76 @@ +package com.miraclegarden.smsmessage.model; + +import java.util.List; + +public class UserInfo { + private String id; + private String username; + private String role; + private boolean isFirstLogin; + private List projectAccesses; + private String parentUserId; + private String googleExcelId; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public boolean isFirstLogin() { + return isFirstLogin; + } + + public void setFirstLogin(boolean firstLogin) { + isFirstLogin = firstLogin; + } + + public List getProjectAccesses() { + return projectAccesses; + } + + public void setProjectAccesses(List projectAccesses) { + this.projectAccesses = projectAccesses; + } + + public String getParentUserId() { + return parentUserId; + } + + public void setParentUserId(String parentUserId) { + this.parentUserId = parentUserId; + } + + public String getGoogleExcelId() { + return googleExcelId; + } + + public void setGoogleExcelId(String googleExcelId) { + this.googleExcelId = googleExcelId; + } + + public boolean hasProjectAccess(String projectType) { + if (projectAccesses == null || projectAccesses.isEmpty()) { + return false; + } + return projectAccesses.contains(projectType); + } +} diff --git a/app/src/main/java/com/miraclegarden/smsmessage/network/ApiService.java b/app/src/main/java/com/miraclegarden/smsmessage/network/ApiService.java new file mode 100644 index 0000000..79d8dc7 --- /dev/null +++ b/app/src/main/java/com/miraclegarden/smsmessage/network/ApiService.java @@ -0,0 +1,376 @@ +package com.miraclegarden.smsmessage.network; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.google.gson.Gson; +import com.miraclegarden.smsmessage.model.ApiError; +import com.miraclegarden.smsmessage.model.AppNotificationSettings; +import com.miraclegarden.smsmessage.model.BankInfo; +import com.miraclegarden.smsmessage.model.BankListResponse; +import com.miraclegarden.smsmessage.model.LoginResponse; +import com.miraclegarden.smsmessage.model.UploadNotificationResponse; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +/** + * API服务类 - 封装所有网络请求 + */ +public class ApiService { + private static final String TAG = "ApiService"; + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static final String BASE_URL = "https://www.judy88.xin"; + + private final OkHttpClient httpClient; + private final Gson gson; + private final TokenManager tokenManager; + + public ApiService(Context context) { + this.tokenManager = TokenManager.getInstance(context); + this.gson = new Gson(); + this.httpClient = new OkHttpClient.Builder() + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .build(); + } + + /** + * 登录接口 + */ + public void login(String username, String password, ApiCallback callback) { + try { + JSONObject json = new JSONObject(); + json.put("username", username); + json.put("password", password); + json.put("clientType", "app"); + + Request request = new Request.Builder() + .url(BASE_URL + "/api/auth/login") + .header("Content-Type", "application/json") + .post(RequestBody.create(json.toString(), JSON)) + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage())); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + handleResponse(response, LoginResponse.class, callback); + } + }); + } catch (JSONException e) { + callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage())); + } + } + + /** + * 修改密码 + */ + public void changePassword(String oldPassword, String newPassword, ApiCallback callback) { + try { + JSONObject json = new JSONObject(); + json.put("oldPassword", oldPassword); + json.put("newPassword", newPassword); + + Request request = buildAuthRequest("/api/auth/change-password") + .post(RequestBody.create(json.toString(), JSON)) + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage())); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + callback.onSuccess(null); + } else { + callback.onError(parseError(response)); + } + } + }); + } catch (JSONException e) { + callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage())); + } + } + + /** + * 获取银行账户列表 + */ + public void getBankList(ApiCallback> callback) { + Request request = buildAuthRequest("/api/bank-email-parser/banks") + .get() + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage())); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + handleResponse(response, BankListResponse.class, new ApiCallback() { + @Override + public void onSuccess(BankListResponse result) { + callback.onSuccess(result.getBanks()); + } + + @Override + public void onError(ApiError error) { + callback.onError(error); + } + }); + } + }); + } + + /** + * 创建银行账户 + */ + public void createBank(BankInfo bankInfo, ApiCallback callback) { + try { + JSONObject json = new JSONObject(); + json.put("bankName", bankInfo.getBankName()); + json.put("account", bankInfo.getAccount()); + if (bankInfo.getRemark() != null) { + json.put("remark", bankInfo.getRemark()); + } + + Request request = buildAuthRequest("/api/bank-email-parser/banks") + .post(RequestBody.create(json.toString(), JSON)) + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage())); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + handleResponse(response, BankInfo.class, callback); + } + }); + } catch (JSONException e) { + callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage())); + } + } + + /** + * 更新银行账户 + */ + public void updateBank(String bankId, BankInfo bankInfo, ApiCallback callback) { + try { + JSONObject json = new JSONObject(); + json.put("bankName", bankInfo.getBankName()); + json.put("account", bankInfo.getAccount()); + if (bankInfo.getRemark() != null) { + json.put("remark", bankInfo.getRemark()); + } + + Request request = buildAuthRequest("/api/bank-email-parser/banks/" + bankId) + .patch(RequestBody.create(json.toString(), JSON)) + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage())); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + handleResponse(response, BankInfo.class, callback); + } + }); + } catch (JSONException e) { + callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage())); + } + } + + /** + * 删除银行账户 + */ + public void deleteBank(String bankId, ApiCallback callback) { + Request request = buildAuthRequest("/api/bank-email-parser/banks/" + bankId) + .delete() + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage())); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + callback.onSuccess(null); + } else { + callback.onError(parseError(response)); + } + } + }); + } + + /** + * 获取App通知设置 + */ + public void getAppNotificationSettings(ApiCallback callback) { + Request request = buildAuthRequest("/api/settings/app-notification") + .get() + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage())); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + handleResponse(response, AppNotificationSettings.class, callback); + } + }); + } + + /** + * 更新App通知设置 + */ + public void updateAppNotificationSettings(boolean enabled, ApiCallback callback) { + try { + JSONObject json = new JSONObject(); + json.put("appNotificationAutoProcessEnabled", enabled); + + Request request = buildAuthRequest("/api/settings/app-notification") + .patch(RequestBody.create(json.toString(), JSON)) + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage())); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + callback.onSuccess(null); + } else { + callback.onError(parseError(response)); + } + } + }); + } catch (JSONException e) { + callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage())); + } + } + + /** + * 上传App通知 + */ + public void uploadNotification(String jsonPayload, ApiCallback callback) { + Request request = buildAuthRequest("/api/bills/app-upload") + .post(RequestBody.create(jsonPayload, JSON)) + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage())); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + handleResponse(response, UploadNotificationResponse.class, callback); + } + }); + } + + /** + * 构建带认证的请求 + */ + private Request.Builder buildAuthRequest(String path) { + Request.Builder builder = new Request.Builder() + .url(BASE_URL + path) + .header("Content-Type", "application/json"); + + String authHeader = tokenManager.getAuthHeader(); + if (authHeader != null) { + builder.header("Authorization", authHeader); + } + + return builder; + } + + /** + * 处理响应 + */ + private void handleResponse(Response response, Class clazz, ApiCallback callback) { + try { + if (response.isSuccessful()) { + String body = response.body() != null ? response.body().string() : ""; + T result = gson.fromJson(body, clazz); + callback.onSuccess(result); + } else { + callback.onError(parseError(response)); + } + } catch (Exception e) { + Log.e(TAG, "Response handling error", e); + callback.onError(new ApiError(0, "PARSE_ERROR", e.getMessage())); + } finally { + response.close(); + } + } + + /** + * 解析错误响应 + */ + private ApiError parseError(Response response) { + try { + int code = response.code(); + String body = response.body() != null ? response.body().string() : ""; + + try { + JSONObject json = new JSONObject(body); + String error = json.optString("error", "UNKNOWN_ERROR"); + String message = json.optString("message", body); + return new ApiError(code, error, message); + } catch (JSONException e) { + return new ApiError(code, "HTTP_ERROR", body.isEmpty() ? "HTTP " + code : body); + } + } catch (IOException e) { + return new ApiError(response.code(), "READ_ERROR", e.getMessage()); + } + } + + /** + * API回调接口 + */ + public interface ApiCallback { + void onSuccess(T result); + void onError(ApiError error); + } +} diff --git a/app/src/main/java/com/miraclegarden/smsmessage/network/TokenManager.java b/app/src/main/java/com/miraclegarden/smsmessage/network/TokenManager.java new file mode 100644 index 0000000..03420b3 --- /dev/null +++ b/app/src/main/java/com/miraclegarden/smsmessage/network/TokenManager.java @@ -0,0 +1,84 @@ +package com.miraclegarden.smsmessage.network; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.miraclegarden.smsmessage.model.LoginResponse; +import com.miraclegarden.smsmessage.model.UserInfo; + +/** + * JWT Token管理器 + */ +public class TokenManager { + private static final String PREFS_NAME = "auth_prefs"; + private static final String KEY_TOKEN = "jwt_token"; + private static final String KEY_USER_ID = "user_id"; + private static final String KEY_USERNAME = "username"; + private static final String KEY_ROLE = "role"; + private static final String KEY_IS_FIRST_LOGIN = "is_first_login"; + + private final SharedPreferences prefs; + private static TokenManager instance; + + private TokenManager(Context context) { + prefs = context.getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + } + + public static synchronized TokenManager getInstance(Context context) { + if (instance == null) { + instance = new TokenManager(context); + } + return instance; + } + + public void saveLoginData(LoginResponse response) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(KEY_TOKEN, response.getToken()); + + UserInfo user = response.getUser(); + if (user != null) { + editor.putString(KEY_USER_ID, user.getId()); + editor.putString(KEY_USERNAME, user.getUsername()); + editor.putString(KEY_ROLE, user.getRole()); + editor.putBoolean(KEY_IS_FIRST_LOGIN, user.isFirstLogin()); + } + editor.apply(); + } + + public String getToken() { + return prefs.getString(KEY_TOKEN, null); + } + + public String getAuthHeader() { + String token = getToken(); + return token != null ? "Bearer " + token : null; + } + + public boolean isLoggedIn() { + return getToken() != null; + } + + public String getUserId() { + return prefs.getString(KEY_USER_ID, null); + } + + public String getUsername() { + return prefs.getString(KEY_USERNAME, null); + } + + public String getRole() { + return prefs.getString(KEY_ROLE, null); + } + + public boolean isFirstLogin() { + return prefs.getBoolean(KEY_IS_FIRST_LOGIN, false); + } + + public void setFirstLoginComplete() { + prefs.edit().putBoolean(KEY_IS_FIRST_LOGIN, false).apply(); + } + + public void clear() { + prefs.edit().clear().apply(); + } +}