1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-11-23 11:46:03 -06:00

Add timetable widget (#84)

This commit is contained in:
Rafał Borcz 2018-04-24 21:27:45 +02:00 committed by Mikołaj Pich
parent 6927ac0e4f
commit 6fcf09e2b7
25 changed files with 498 additions and 18 deletions

View File

@ -39,12 +39,24 @@
android:label="@string/activity_dashboard_text" />
<service
android:name=".services.SyncJob"
android:name=".services.jobs.SyncJob"
android:exported="false">
<intent-filter>
<action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE" />
</intent-filter>
</service>
<service
android:name=".services.widgets.TimetableWidgetServices"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver android:name=".ui.widgets.TimetableWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_provider" />
</receiver>
<meta-data
android:name="io.fabric.ApiKey"

View File

@ -68,6 +68,16 @@ public class Repository implements RepositoryContract {
return sharedPref.getCurrentUserId();
}
@Override
public void setTimetableWidgetState(boolean nextDay) {
sharedPref.setTimetableWidgetState(nextDay);
}
@Override
public boolean getTimetableWidgetState() {
return sharedPref.getTimetableWidgetState();
}
@Override
public int getStartupTab() {
return sharedPref.getStartupTab();

View File

@ -23,6 +23,10 @@ public interface RepositoryContract extends ResourcesContract, AccountSyncContra
int getStartupTab();
void setTimetableWidgetState(boolean nextDay);
boolean getTimetableWidgetState();
boolean isServicesEnable();
boolean isNotifyEnable();

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.data.db.shared;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
@ -16,6 +17,8 @@ public class SharedPref implements SharedPrefContract {
private static final String SHARED_KEY_USER_ID = "USER_ID";
private static final String SHARED_KEY_TIMETABLE_WIDGET_STATE = "TIMETABLE_WIDGET_STATE";
private final SharedPreferences appSharedPref;
private final SharedPreferences settingsSharedPref;
@ -36,6 +39,17 @@ public class SharedPref implements SharedPrefContract {
appSharedPref.edit().putLong(SHARED_KEY_USER_ID, userId).apply();
}
@SuppressLint("ApplySharedPref")
@Override
public void setTimetableWidgetState(boolean nextDay) {
appSharedPref.edit().putBoolean(SHARED_KEY_TIMETABLE_WIDGET_STATE, nextDay).commit();
}
@Override
public boolean getTimetableWidgetState() {
return appSharedPref.getBoolean(SHARED_KEY_TIMETABLE_WIDGET_STATE, false);
}
@Override
public int getStartupTab() {
return Integer.parseInt(settingsSharedPref.getString(SettingsFragment.SHARED_KEY_START_TAB, "2"));

View File

@ -6,6 +6,10 @@ public interface SharedPrefContract {
void setCurrentUserId(long userId);
void setTimetableWidgetState(boolean nextDay);
boolean getTimetableWidgetState();
int getStartupTab();
int getServicesInterval();

View File

@ -9,7 +9,9 @@ import io.github.wulkanowy.WulkanowyApp;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.di.annotations.ApplicationContext;
import io.github.wulkanowy.di.modules.ApplicationModule;
import io.github.wulkanowy.services.SyncJob;
import io.github.wulkanowy.services.jobs.SyncJob;
import io.github.wulkanowy.ui.widgets.TimetableWidgetFactory;
import io.github.wulkanowy.ui.widgets.TimetableWidgetProvider;
@Singleton
@Component(modules = ApplicationModule.class)
@ -23,4 +25,8 @@ public interface ApplicationComponent {
void inject(WulkanowyApp wulkanowyApp);
void inject(SyncJob syncJob);
void inject(TimetableWidgetFactory timetableWidgetFactory);
void inject(TimetableWidgetProvider timetableWidgetProvider);
}

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.services;
package io.github.wulkanowy.services.jobs;
import android.app.PendingIntent;
import android.content.Context;
@ -24,13 +24,12 @@ import io.github.wulkanowy.R;
import io.github.wulkanowy.WulkanowyApp;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.data.db.dao.entities.Grade;
import io.github.wulkanowy.services.notifies.GradeNotify;
import io.github.wulkanowy.ui.main.MainActivity;
import io.github.wulkanowy.utils.LogUtils;
public class SyncJob extends SimpleJobService {
public static final String EXTRA_INTENT_KEY = "cardId";
public static final String JOB_TAG = "SyncJob";
private List<Grade> gradeList = new ArrayList<>();
@ -94,7 +93,8 @@ public class SyncJob extends SimpleJobService {
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(getResources().getColor(R.color.colorPrimary))
.setContentIntent(PendingIntent.getActivity(getApplicationContext(), 0,
MainActivity.getStartIntent(getApplicationContext()).putExtra(EXTRA_INTENT_KEY, 0)
MainActivity.getStartIntent(getApplicationContext())
.putExtra(MainActivity.EXTRA_CARD_ID_KEY, 0)
, 0
))
.build());

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.services;
package io.github.wulkanowy.services.notifies;
import android.annotation.TargetApi;
import android.app.Notification;
@ -8,11 +8,11 @@ import android.content.Context;
import io.github.wulkanowy.R;
class GradeNotify extends NotificationService {
public class GradeNotify extends NotificationService {
private static final String CHANNEL_ID = "Grade_Notify";
GradeNotify(Context context) {
public GradeNotify(Context context) {
super(context);
}

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.services;
package io.github.wulkanowy.services.notifies;
import android.annotation.TargetApi;
@ -21,11 +21,11 @@ public class NotificationService {
this.context = context;
}
void notify(Notification notification) {
public void notify(Notification notification) {
getManager().notify(new Random().nextInt(1000), notification);
}
NotificationCompat.Builder notificationBuilder() {
public NotificationCompat.Builder notificationBuilder() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel();
}

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.services.widgets;
import android.content.Intent;
import android.widget.RemoteViewsService;
import io.github.wulkanowy.ui.widgets.TimetableWidgetFactory;
public class TimetableWidgetServices extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new TimetableWidgetFactory(getApplicationContext());
}
}

View File

@ -16,7 +16,6 @@ import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.github.wulkanowy.R;
import io.github.wulkanowy.services.SyncJob;
import io.github.wulkanowy.ui.base.BaseActivity;
import io.github.wulkanowy.ui.main.attendance.AttendanceFragment;
import io.github.wulkanowy.ui.main.dashboard.DashboardFragment;
@ -27,6 +26,8 @@ import io.github.wulkanowy.ui.main.timetable.TimetableFragment;
public class MainActivity extends BaseActivity implements MainContract.View,
AHBottomNavigation.OnTabSelectedListener, OnFragmentIsReadyListener {
public static final String EXTRA_CARD_ID_KEY = "cardId";
@BindView(R.id.main_activity_nav)
AHBottomNavigation bottomNavigation;
@ -54,7 +55,7 @@ public class MainActivity extends BaseActivity implements MainContract.View,
getActivityComponent().inject(this);
setButterKnife(ButterKnife.bind(this));
presenter.onStart(this, getIntent().getIntExtra(SyncJob.EXTRA_INTENT_KEY, -1));
presenter.onStart(this, getIntent().getIntExtra(EXTRA_CARD_ID_KEY, -1));
}
@Override

View File

@ -5,7 +5,7 @@ import android.os.Bundle;
import android.support.v7.preference.PreferenceFragmentCompat;
import io.github.wulkanowy.R;
import io.github.wulkanowy.services.SyncJob;
import io.github.wulkanowy.services.jobs.SyncJob;
public class SettingsFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener {

View File

@ -5,8 +5,8 @@ import android.os.Bundle;
import javax.inject.Inject;
import butterknife.ButterKnife;
import io.github.wulkanowy.services.NotificationService;
import io.github.wulkanowy.services.SyncJob;
import io.github.wulkanowy.services.jobs.SyncJob;
import io.github.wulkanowy.services.notifies.NotificationService;
import io.github.wulkanowy.ui.base.BaseActivity;
import io.github.wulkanowy.ui.login.LoginActivity;
import io.github.wulkanowy.ui.main.MainActivity;

View File

@ -0,0 +1,138 @@
package io.github.wulkanowy.ui.widgets;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import io.github.wulkanowy.R;
import io.github.wulkanowy.WulkanowyApp;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.data.db.dao.entities.TimetableLesson;
import io.github.wulkanowy.data.db.dao.entities.Week;
import io.github.wulkanowy.utils.TimeUtils;
public class TimetableWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
private Context context;
private List<TimetableLesson> lessonList = new ArrayList<>();
@Inject
RepositoryContract repository;
public TimetableWidgetFactory(Context context) {
this.context = context;
}
private void inject() {
if (repository == null) {
((WulkanowyApp) context).getApplicationComponent().inject(this);
}
}
@Override
public void onCreate() {
// do nothing
}
@Override
public void onDataSetChanged() {
inject();
lessonList = new ArrayList<>();
if (repository.getCurrentUserId() != 0) {
Week week = repository.getWeek(TimeUtils.getDateOfCurrentMonday(true));
int valueOfDay = TimeUtils.getTodayOrNextDayValue(repository.getTimetableWidgetState());
if (valueOfDay != 5 && valueOfDay != 6 && week != null) {
week.resetDayList();
lessonList = week.getDayList().get(valueOfDay).getTimetableLessons();
}
}
}
@Override
public void onDestroy() {
// do nothing
}
@Override
public int getCount() {
return lessonList.size();
}
@Override
public RemoteViews getViewAt(int position) {
if (position == AdapterView.INVALID_POSITION) {
return null;
}
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.timetable_widget_item);
views.setTextViewText(R.id.timetable_widget_item_subject, getSubjectName(position));
views.setTextViewText(R.id.timetable_widget_item_time, getTimeText(position));
views.setTextViewText(R.id.timetable_widget_item_room, getRoomText(position));
if (!getDescriptionText(position).isEmpty()) {
views.setTextViewText(R.id.timetable_widget_item_description, getDescriptionText(position));
} else {
views.setViewVisibility(R.id.timetable_widget_item_description, View.GONE);
}
views.setOnClickFillInIntent(R.id.timetable_widget_item_container, new Intent());
return views;
}
@Override
public RemoteViews getLoadingView() {
return null;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean hasStableIds() {
return true;
}
private String getSubjectName(int position) {
return lessonList.get(position).getSubject();
}
private String getRoomText(int position) {
TimetableLesson lesson = lessonList.get(position);
if (!lesson.getRoom().isEmpty()) {
return context.getString(R.string.timetable_dialog_room) + " " + lesson.getRoom();
}
return lesson.getRoom();
}
private String getTimeText(int position) {
TimetableLesson lesson = lessonList.get(position);
return lesson.getStartTime() + " - " + lesson.getEndTime();
}
private String getDescriptionText(int position) {
return StringUtils.capitalize(lessonList.get(position).getDescription());
}
}

View File

@ -0,0 +1,102 @@
package io.github.wulkanowy.ui.widgets;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.TaskStackBuilder;
import android.widget.RemoteViews;
import javax.inject.Inject;
import io.github.wulkanowy.R;
import io.github.wulkanowy.WulkanowyApp;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.services.widgets.TimetableWidgetServices;
import io.github.wulkanowy.ui.main.MainActivity;
import io.github.wulkanowy.utils.TimeUtils;
public class TimetableWidgetProvider extends AppWidgetProvider {
@Inject
RepositoryContract repository;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
inject(context);
for (int appWidgetId : appWidgetIds) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.timetable_widget);
setViews(views, context, appWidgetId);
setToggleIntent(views, context);
setTemplateIntent(views, context);
updateWidget(views, appWidgetManager, appWidgetId);
}
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
@Override
public void onReceive(final Context context, Intent intent) {
super.onReceive(context, intent);
inject(context);
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(intent.getAction())) {
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName thisWidget = new ComponentName(context.getPackageName(),
TimetableWidgetProvider.class.getName());
final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
repository.setTimetableWidgetState(!repository.getTimetableWidgetState());
onUpdate(context, appWidgetManager, appWidgetIds);
}
}
private void setToggleIntent(RemoteViews views, Context context) {
Intent refreshIntent = new Intent(context, TimetableWidgetProvider.class);
refreshIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.timetable_widget_toggle, pendingIntent);
}
private void setTemplateIntent(RemoteViews views, Context context) {
Intent intent = MainActivity.getStartIntent(context);
intent.putExtra(MainActivity.EXTRA_CARD_ID_KEY, 3);
PendingIntent pendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(intent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
views.setPendingIntentTemplate(R.id.timetable_widget_list, pendingIntent);
}
private void setViews(RemoteViews views, Context context, int appWidgetId) {
Intent intent = new Intent(context, TimetableWidgetServices.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
views.setRemoteAdapter(appWidgetId, R.id.timetable_widget_list, intent);
views.setEmptyView(R.id.timetable_widget_list, R.id.timetable_widget_empty);
boolean nextDay = repository.getTimetableWidgetState();
String toggleText = context.getString(nextDay ? R.string.widget_timetable_tomorrow
: R.string.widget_timetable_today);
views.setTextViewText(R.id.timetable_widget_toggle, toggleText);
views.setTextViewText(R.id.timetable_widget_date, TimeUtils.getTodayOrNextDay(nextDay));
}
private void updateWidget(RemoteViews views, AppWidgetManager appWidgetManager, int appWidgetId) {
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetable_widget_list);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
private void inject(Context context) {
if (repository == null) {
((WulkanowyApp) context.getApplicationContext()).getApplicationComponent().inject(this);
}
}
}

View File

@ -86,4 +86,20 @@ public final class TimeUtils {
}
return currentDate.format(formatter);
}
public static int getTodayOrNextDayValue(boolean nextDay) {
DayOfWeek day = LocalDate.now().getDayOfWeek();
if (nextDay) {
if (day == DayOfWeek.SUNDAY) {
return 0;
}
return day.getValue();
}
return day.getValue() - 1;
}
public static String getTodayOrNextDay(boolean nextDay) {
LocalDate current = LocalDate.now();
return nextDay ? current.plusDays(1).format(formatter) : current.format(formatter);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/widget_bar_height"
android:background="@color/colorPrimary">
<Button
android:id="@+id/timetable_widget_toggle"
android:layout_width="100dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:backgroundTint="@color/colorPrimaryDark"
android:text="@string/widget_timetable_today"
android:textColor="@android:color/white"
android:textSize="12sp" />
<TextView
android:id="@+id/timetable_widget_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="5dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_toEndOf="@id/timetable_widget_title"
android:layout_toRightOf="@id/timetable_widget_title"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@android:color/white"
android:textSize="13sp" />
<TextView
android:id="@+id/timetable_widget_title"
android:layout_width="match_parent"
android:layout_height="@dimen/widget_bar_height"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_toLeftOf="@id/timetable_widget_toggle"
android:layout_toStartOf="@id/timetable_widget_toggle"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:text="@string/timetable_text"
android:textColor="@android:color/white"
android:textSize="20sp" />
</RelativeLayout>
<ListView
android:id="@+id/timetable_widget_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/widget_bar_height" />
<TextView
android:id="@+id/timetable_widget_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/widget_timetable_no_lesson"
android:textColor="@android:color/black"
android:textSize="20sp" />
</FrameLayout>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/timetable_widget_item_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="45dp"
android:orientation="horizontal">
<TextView
android:id="@+id/timetable_widget_item_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:text="@string/app_name"
android:textColor="@color/second_text_color"
android:textSize="14sp" />
<TextView
android:id="@+id/timetable_widget_item_subject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="25dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="25dp"
android:layout_marginStart="15dp"
android:layout_marginTop="5dp"
android:layout_toEndOf="@id/timetable_widget_item_time"
android:layout_toRightOf="@id/timetable_widget_item_time"
android:text="@string/app_name"
android:textColor="@color/second_text_color"
android:textSize="14sp" />
<TextView
android:id="@+id/timetable_widget_item_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/timetable_widget_item_subject"
android:layout_marginBottom="5dp"
android:layout_marginEnd="25dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="25dp"
android:layout_marginStart="15dp"
android:layout_marginTop="3dp"
android:layout_toEndOf="@id/timetable_widget_item_time"
android:layout_toRightOf="@id/timetable_widget_item_time"
android:text="@string/app_name"
android:textColor="@color/colorPrimaryDark"
android:textSize="12sp" />
<TextView
android:id="@+id/timetable_widget_item_room"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/timetable_widget_item_time"
android:layout_marginBottom="5dp"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="3dp"
android:text="@string/app_name"
android:textColor="@color/second_text_color"
android:textSize="12sp" />
</RelativeLayout>

View File

@ -124,4 +124,7 @@
<string name="pref_services_mobile_data">Synchronizacja tylko przez WiFi</string>
<string name="notify_grade_channel">Nowe oceny</string>
<string name="widget_timetable_no_lesson">Brak lekcji</string>
<string name="widget_timetable_today">Dziś</string>
<string name="widget_timetable_tomorrow">Jutro</string>
</resources>

View File

@ -11,6 +11,7 @@
<color name="two_grade">#CE9AD2</color>
<color name="one_grade">#d32f2f</color>
<color name="secondary_text">#4c4c4c</color>
<color name="second_text_color">#333</color>
<color name="default_grade">#cdcdcd</color>
<color name="free_day">#eee</color>
</resources>

View File

@ -4,4 +4,5 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="widget_bar_height">60dp</dimen>
</resources>

View File

@ -120,4 +120,7 @@
<string name="pref_services_mobile_data">Synchronization via WiFi only</string>
<string name="notify_grade_channel">New grades</string>
<string name="widget_timetable_no_lesson">No lessons</string>
<string name="widget_timetable_today">Today</string>
<string name="widget_timetable_tomorrow">Tomorrow</string>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/timetable_widget"
android:minHeight="100dp"
android:minWidth="150dp"
android:previewImage="@drawable/widget_timetable_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="3600000"
android:widgetCategory="home_screen" />

View File

@ -6,7 +6,7 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.0'
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.1"
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'