News Show Notification on Sidebar

This commit is contained in:
Caesar2011
2018-11-18 19:23:22 +01:00
parent 35141bc1ec
commit 671c9bd86a
16 changed files with 211 additions and 261 deletions

View File

@@ -2,7 +2,6 @@ package de.sebse.fuplanner;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -16,6 +15,8 @@ import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.zip.Inflater;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
@@ -44,14 +45,17 @@ import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.KVV.KVVListener;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.services.News.NewsManager;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.NewAsyncQueue;
import de.sebse.fuplanner.tools.Preferences;
import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.RequestPermissionsResultListener;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
import de.sebse.fuplanner.tools.types.News;
public class MainActivity extends AppCompatActivity
implements MainActivityListener, KVVListener,
@@ -77,13 +81,14 @@ public class MainActivity extends AppCompatActivity
private FragmentManager mFragmentManager;
private GoogleAuth mGoogleAuth;
private KVV mKVV;
private NewsManager mNewsManager;
private CanteenBrowser mCanteenBrowser;
private final Logger log = new Logger(this);
private NavigationView mNavigationView;
private int mFragmentPage = FRAGMENT_NONE;
@NotNull
private String mFragmentData = "";
private CanteenBrowser mCanteenBrowser;
private final HashMap<String, RequestPermissionsResultListener> permissionListeners = new HashMap<>();
private boolean mOfflineBanner;
private final NewAsyncQueue mQueue = new NewAsyncQueue();
@@ -117,7 +122,6 @@ public class MainActivity extends AppCompatActivity
desiredPage = FRAGMENT_LOGIN;
desiredData = "";
}
log.d("desired", desiredPage, desiredData);
updateNavigation();
changeFragment(desiredPage, desiredData);
}
@@ -283,13 +287,12 @@ public class MainActivity extends AppCompatActivity
return this.mGoogleAuth;
}
/*@Deprecated
public de.sebse.fuplanner.services.KVV.KVV getKVV() {
if (this.mKVV == null) {
this.mKVV = new de.sebse.fuplanner.services.KVV.KVV(this);
public NewsManager getNewsManager() {
if (this.mNewsManager == null) {
this.mNewsManager = new NewsManager(this);
}
return this.mKVV;
}*/
return this.mNewsManager;
}
public KVV getKVV() {
if (this.mKVV == null) {
@@ -361,6 +364,8 @@ public class MainActivity extends AppCompatActivity
fragment = CanteensFragment.newInstance();
break;
case FRAGMENT_NEWS:
Preferences.setLong(this, R.string.pref_last_visited_news, System.currentTimeMillis());
updateNavigation();
fragment = NewsFragment.newInstance();
break;
case FRAGMENT_PREFERENCES:
@@ -469,7 +474,7 @@ public class MainActivity extends AppCompatActivity
}
private void afterAnyMenuInflate(boolean isLoggedIn, Runnable done) {
int MAX_COUNT = isLoggedIn ? 2 : 1;
int MAX_COUNT = isLoggedIn ? 3 : 2;
final int[] count = {0};
if (isLoggedIn) {
getKVV().modules().list().recv(success -> {
@@ -484,9 +489,9 @@ public class MainActivity extends AppCompatActivity
i++;
}
if (++count[0] == MAX_COUNT) done.run();
}, msg -> {
}, error -> {
if (++count[0] == MAX_COUNT) done.run();
log.e(msg);
log.e(error);
});
}
getCanteenBrowser().getCanteens(success -> {
@@ -500,9 +505,28 @@ public class MainActivity extends AppCompatActivity
i++;
}
if (++count[0] == MAX_COUNT) done.run();
}, msg -> {
}, error -> {
if (++count[0] == MAX_COUNT) done.run();
log.e(msg);
log.e(error);
});
getNewsManager().recv(success -> {
long lastVisited = Preferences.getLong(this, R.string.pref_last_visited_news);
int i = 0;
for (News news: success) {
if (news.getDate() > lastVisited) i++;
}
if (i > 0) {
MenuItem menuItem = mNavigationView.getMenu().findItem(R.id.nav_news);
menuItem.setIcon(R.drawable.ic_sms_failed);
View view = View.inflate(this, R.layout.action_icon_number, null);
TextView v = view.findViewById(R.id.number);
((TextView) view.findViewById(R.id.number)).setText(String.format(Locale.getDefault(), "%d", i));
menuItem.setActionView(view);
}
if (++count[0] == MAX_COUNT) done.run();
}, error -> {
if (++count[0] == MAX_COUNT) done.run();
log.e(error);
});
}

View File

@@ -38,6 +38,7 @@ public class NewsFragment extends Fragment {
private Logger log = new Logger(this);
private MainActivityListener mListener;
private NewsAdapter mAdapter;
private NewsList dates = new NewsList();
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
@@ -71,7 +72,7 @@ public class NewsFragment extends Fragment {
}
else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
refresh();
refresh(false);
}
@Override
@@ -80,24 +81,6 @@ public class NewsFragment extends Fragment {
mListener = null;
}
private String loadJSONFromAsset() {
if (getActivity() == null)
return null;
String json = null;
try {
InputStream is = getActivity().getResources().openRawResource(R.raw.news);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
return json;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -108,45 +91,18 @@ public class NewsFragment extends Fragment {
recyclerView.setLayoutManager(new LinearLayoutManager(context));
mAdapter = new NewsAdapter();
recyclerView.setAdapter(mAdapter);
refresh();
refresh(false);
return view;
}
private void refresh() {
if (mAdapter == null)
private void refresh(boolean forceRefresh) {
if (mListener == null)
return;
String fromAsset = loadJSONFromAsset();
if (fromAsset == null)
return;
String language = Locale.getDefault().getLanguage();
try {
JSONObject json = new JSONObject(fromAsset);
JSONArray news = json.getJSONArray("news");
NewsList dates = new NewsList();
for (int i = news.length() - 1; i >= 0; i--) {
String title = news.getJSONObject(i).optString("title_"+language, null);
if (title == null)
title = news.getJSONObject(i).getString("title");
String categoryString = news.getJSONObject(i).getString("category");
int category;
if (categoryString.equals("CATEGORY_TRICKS"))
category = News.CATEGORY_TRICKS;
else
category = News.CATEGORY_UPDATE;
String dateString = news.getJSONObject(i).getString("date");
long date = UtilsDate.stringToMillis(dateString, "dd.MM.yyyy");
String text = news.getJSONObject(i).optString("text_"+language, null);
if (text == null)
text = news.getJSONObject(i).getString("text");
News event = new News(title, category, date, text);
dates.add(event);
}
mAdapter.setNews(dates);
} catch (JSONException e) {
e.printStackTrace();
}
mListener.getNewsManager().recv(success -> {
if (mAdapter != null)
mAdapter.setNews(success);
}, log::e, forceRefresh);
}
}

View File

@@ -42,7 +42,7 @@ public class GoogleAuth {
private void connect() {
if (this.isUnavailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
Log.w(TAG, "STATUS: Google auth not available!");
return;
}
this.mCredentialsClient = getClient();
@@ -54,7 +54,7 @@ public class GoogleAuth {
public void getLoginState(final CredentialsListener credentialsListener) {
if (this.isUnavailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
Log.w(TAG, "STATUS: Google auth not available!");
credentialsListener.onCredentials(null);
return;
}
@@ -102,7 +102,7 @@ public class GoogleAuth {
public void setLoginState(String username, String password) {
if (this.isUnavailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
Log.w(TAG, "STATUS: Google auth not available!");
Toast.makeText(activity, "Google auth not available!", Toast.LENGTH_SHORT).show();
return;
}
@@ -133,7 +133,7 @@ public class GoogleAuth {
public void deleteLoginState(String username, String password) {
if (this.isUnavailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
Log.w(TAG, "STATUS: Google auth not available!");
return;
}
if (!this.isConnected)
@@ -161,7 +161,7 @@ public class GoogleAuth {
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult:" + requestCode + ":" + resultCode + ":" + data);
Log.w(TAG, "onActivityResult:" + requestCode + ":" + resultCode + ":" + data);
switch (requestCode) {
case RequestCode.RC_HINT:
@@ -173,12 +173,12 @@ public class GoogleAuth {
if (mCredentialsListener != null)
this.mCredentialsListener.onCredentials(new Credentials(credential.getId(), credential.getPassword()));
else
Log.d(TAG, "No Credentials Listener");
Log.w(TAG, "No Credentials Listener");
} else {
if (mCredentialsListener != null)
this.mCredentialsListener.onCredentials(null);
else
Log.d(TAG, "No Credentials Listener");
Log.w(TAG, "No Credentials Listener");
Log.e(TAG, "Credential Read: NOT OK");
showToast("Credential Read Failed");
}
@@ -187,7 +187,7 @@ public class GoogleAuth {
break;
case RequestCode.RC_SAVE:
if (resultCode == RESULT_OK) {
Log.d(TAG, "Credential Save: OK");
Log.w(TAG, "Credential Save: OK");
showToast("Credential Save Success");
} else {
Log.e(TAG, "Credential Save: NOT OK");
@@ -208,7 +208,7 @@ public class GoogleAuth {
return;
}
Log.d(TAG, "Resolving: " + rae);
Log.w(TAG, "Resolving: " + rae);
try {
rae.startResolutionForResult(this.activity, requestCode);
mIsResolving = true;

View File

@@ -20,7 +20,6 @@ final public class ModulesDetails extends Part<Pair<Modules.Module, Boolean>> {
@Override
protected void recv(final Modules.Module module, final NetworkCallback<Pair<Modules.Module, Boolean>> callback, final NetworkErrorCallback errorCallback, final boolean forceRefresh, final int retries) {
log.d(mLogin.getLoginToken());
final int[] returned = {0};
AtomicReference<NetworkError> lastError = new AtomicReference<>(null);
NetworkCallback<Modules.Module> successCb = success -> {

View File

@@ -0,0 +1,91 @@
package de.sebse.fuplanner.services.News;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
import de.sebse.fuplanner.tools.types.NewsList;
public class NewsManager {
private NewsList mNewsList;
private Context mContext;
public NewsManager(Context context) {
this.mContext = context;
}
public void recv(final NetworkCallback<NewsList> callback, final NetworkErrorCallback errorCallback) {
recv(callback, errorCallback, false);
}
public void recv(final NetworkCallback<NewsList> callback, final NetworkErrorCallback errorCallback, final boolean forceRefresh) {
if (!forceRefresh && mNewsList != null) {
callback.onResponse(mNewsList);
return;
}
String fromAsset = loadJSONFromAsset();
if (fromAsset == null)
return;
String language = Locale.getDefault().getLanguage();
JSONArray news;
NewsList dates = new NewsList();
try {
JSONObject json = new JSONObject(fromAsset);
news = json.getJSONArray("news");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(300100, 500, "Parsing news list failed!"));
return;
}
for (int i = news.length() - 1; i >= 0; i--) {
try {
String title = news.getJSONObject(i).optString("title_" + language, null);
if (title == null)
title = news.getJSONObject(i).getString("title");
String categoryString = news.getJSONObject(i).getString("category");
int category;
if (categoryString.equals("CATEGORY_TRICKS"))
category = de.sebse.fuplanner.tools.types.News.CATEGORY_TRICKS;
else
category = de.sebse.fuplanner.tools.types.News.CATEGORY_UPDATE;
String dateString = news.getJSONObject(i).getString("date");
long date = UtilsDate.stringToMillis(dateString, "dd.MM.yyyy");
String text = news.getJSONObject(i).optString("text_" + language, null);
if (text == null)
text = news.getJSONObject(i).getString("text");
de.sebse.fuplanner.tools.types.News event = new de.sebse.fuplanner.tools.types.News(title, category, date, text);
dates.add(event);
} catch (JSONException e) {
e.printStackTrace();
}
}
mNewsList = dates;
callback.onResponse(mNewsList);
}
private String loadJSONFromAsset() {
try {
InputStream is = mContext.getResources().openRawResource(R.raw.news);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
return new String(buffer, "UTF-8");
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
}
}

View File

@@ -5,6 +5,7 @@ import androidx.annotation.StringRes;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser;
import de.sebse.fuplanner.services.GoogleAuth.GoogleAuth;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.News.NewsManager;
public interface MainActivityListener {
void onTitleTextChange(String newTitle);
@@ -21,6 +22,8 @@ public interface MainActivityListener {
CanteenBrowser getCanteenBrowser();
NewsManager getNewsManager();
@Deprecated
void onRefreshCompleted(boolean isFailed);

View File

@@ -2,6 +2,7 @@ package de.sebse.fuplanner.tools;
import android.content.Context;
import androidx.annotation.ArrayRes;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
public class Preferences {
@@ -9,4 +10,14 @@ public class Preferences {
String[] strings = context.getResources().getStringArray(key);
return PreferenceManager.getDefaultSharedPreferences(context).getString(strings[0], strings[1]);
}
public static long getLong(Context context, @StringRes int key) {
String string = context.getResources().getString(key);
return PreferenceManager.getDefaultSharedPreferences(context).getLong(string, 0);
}
public static void setLong(Context context, @StringRes int key, long value) {
String string = context.getResources().getString(key);
PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(string, value).apply();
}
}

View File

@@ -5,5 +5,5 @@
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2z"/>
android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,16L6,16l-2,2L4,4h16v12z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,14h-2v-2h2v2zM13,10h-2L11,6h2v4z"/>
</vector>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="4dp"
android:color="@color/colorFUBlue" />
<solid
android:color="@color/colorFUBlue"
/>
<corners android:radius="360dp" />
<padding
android:left="10dp"
android:top="5dp"
android:right="10dp"
android:bottom="5dp">
</padding>
</shape>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:id="@+id/number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_blue_filled"
android:textColor="@color/colorFUWhite"
tools:text="4"/>
</LinearLayout>

View File

@@ -9,7 +9,7 @@
android:orderInCategory="200" />
<item
android:id="@+id/nav_news"
android:icon="@drawable/ic_chat_bubble"
android:icon="@drawable/ic_chat_bubble_outline"
android:title="@string/news"
android:orderInCategory="400"/>
</group>

View File

@@ -24,7 +24,7 @@
android:orderInCategory="300"/>
<item
android:id="@+id/nav_news"
android:icon="@drawable/ic_chat_bubble"
android:icon="@drawable/ic_chat_bubble_outline"
android:title="@string/news"
android:orderInCategory="400"/>
</group>

View File

@@ -12,7 +12,4 @@
<item>employee</item>
<item>other</item>
</string-array>
</resources>

View File

@@ -6,4 +6,6 @@
</string-array>
<string name="pref_price_group" translatable="false">pref_price_group</string>
<string name="pref_price_group_default" translatable="false">all</string>
<string name="pref_last_visited_news" translatable="false">pref_last_visited_news</string>
</resources>

View File

@@ -1,178 +0,0 @@
package de.sebse.fuplanner.services.GoogleAuth;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.credentials.Credential;
import com.google.android.gms.auth.api.credentials.CredentialRequest;
import com.google.android.gms.auth.api.credentials.CredentialRequestResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.FragmentActivity;
import static android.app.Activity.RESULT_OK;
/**
* Created by Sebastian on 06.11.2017.
*/
public class GoogleAuth {
// https://developers.google.com/identity/smartlock-passwords/android/retrieve-credentials
private static final String TAG = "GoogleAuth";
private final FragmentActivity activity;
private GoogleApiClient mCredentialsClient;
public GoogleAuth(FragmentActivity activity) {
this.activity = activity;
}
public void connect(final ConnectedListener listener) {
if (!this.isAvailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
listener.connected();
return;
}
this.mCredentialsClient = getClient(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(@Nullable Bundle bundle) {
listener.connected();
}
@Override
public void onConnectionSuspended(int i) {
}
}, new GoogleApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
}
});
}
public void getLoginState(final CredentialsListener credentialsListener) {
if (!this.isAvailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
credentialsListener.onCredentials(null);
return;
}
CredentialRequest mCredentialRequest = new CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.build();
Auth.CredentialsApi.request(this.mCredentialsClient, mCredentialRequest).setResultCallback(
new ResultCallback<CredentialRequestResult>() {
@Override
public void onResult(@NonNull CredentialRequestResult credentialRequestResult) {
if (credentialRequestResult.getStatus().isSuccess()) {
// See "Handle successful credential requests"
Credential credential = credentialRequestResult.getCredential();
credentialsListener.onCredentials(new Credentials(credential.getId(), credential.getPassword()));
} else {
// See "Handle unsuccessful and incomplete credential requests"
credentialsListener.onCredentials(null);
}
}
}
);
}
public void setLoginState(String username, String password) {
if (!this.isAvailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
Toast.makeText(activity, "Google auth not available!", Toast.LENGTH_SHORT).show();
return;
}
Credential credential = new Credential.Builder(username)
.setPassword(password)
.build();
Auth.CredentialsApi.save(mCredentialsClient, credential).setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(@NonNull Status status) {
if (status.isSuccess()) {
Log.d(TAG, "SAVE: OK");
Toast.makeText(activity, "Credentials saved", Toast.LENGTH_SHORT).show();
} else {
if (status.hasResolution()) {
// Try to resolve the save request. This will prompt the user if
// the credential is new.
try {
status.startResolutionForResult(activity, RequestCode.RC_SAVE);
} catch (IntentSender.SendIntentException e) {
// Could not resolve the request
Log.e(TAG, "STATUS: Failed to send resolution.", e);
Toast.makeText(activity, "Save failed", Toast.LENGTH_SHORT).show();
}
} else {
// Request has no resolution
Toast.makeText(activity, "Save failed", Toast.LENGTH_SHORT).show();
}
}
}
}
);
}
public void deleteLoginState(String username, String password) {
if (!this.isAvailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
return;
}
Credential credential = new Credential.Builder(username)
.setPassword(password)
.build();
Auth.CredentialsApi.delete(mCredentialsClient, credential).setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
if (status.isSuccess()) {
// Credential was deleted successfully
}
}
}
);
}
private boolean isAvailable() {
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this.activity) == ConnectionResult.SUCCESS;
}
private GoogleApiClient getClient(@NonNull GoogleApiClient.ConnectionCallbacks connectionCallbacks, @NonNull GoogleApiClient.OnConnectionFailedListener failedListener) {
return new GoogleApiClient.Builder(this.activity)
.addConnectionCallbacks(connectionCallbacks)
.enableAutoManage(this.activity, failedListener)
.addApi(Auth.CREDENTIALS_API)
.build();
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RequestCode.RC_SAVE) {
if (resultCode == RESULT_OK) {
Log.d(TAG, "SAVE: OK");
Toast.makeText(activity, "Credentials saved", Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "SAVE: Canceled by user");
}
}
}
}