diff --git a/app/build.gradle b/app/build.gradle index 775b6dad2..676a563e5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -123,4 +123,5 @@ dependencies { implementation 'com.github.duanhong169:colorpicker:1.1.6' implementation 'com.github.pengfeizhou.android.animation:glide-plugin:0.2.16' + implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index df711c6bd..71b0e60c7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -228,6 +228,12 @@ android:label="@string/app_name" android:launchMode="singleTask" /> + . */ +package app.fedilab.android.activities; + + +import android.annotation.SuppressLint; +import android.app.DatePickerDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.DatePicker; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.github.mikephil.charting.charts.LineChart; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import app.fedilab.android.R; +import app.fedilab.android.asynctasks.RetrieveChartsAsyncTask; +import app.fedilab.android.asynctasks.RetrieveFeedsAsyncTask; +import app.fedilab.android.asynctasks.RetrieveStatsAsyncTask; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.client.Entities.Charts; +import app.fedilab.android.client.Entities.Statistics; +import app.fedilab.android.client.Entities.Status; +import app.fedilab.android.drawers.StatusListAdapter; +import app.fedilab.android.helper.FilterToots; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.interfaces.OnRetrieveChartsInterface; +import app.fedilab.android.interfaces.OnRetrieveFeedsInterface; +import app.fedilab.android.interfaces.OnRetrieveStatsInterface; +import app.fedilab.android.services.BackupStatusInDataBaseService; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; +import app.fedilab.android.sqlite.StatusCacheDAO; +import es.dmoral.toasty.Toasty; + + +/** + * Created by Thomas on 28/07/2019. + * Charts for owner activity + */ + +public class OwnerChartsActivity extends BaseActivity implements OnRetrieveChartsInterface { + + + LinearLayoutManager mLayoutManager; + private int style; + private Button settings_time_from, settings_time_to; + private Date dateIni, dateEnd; + private LineChart chart; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK); + switch (theme){ + case Helper.THEME_LIGHT: + setTheme(R.style.AppTheme_NoActionBar_Fedilab); + break; + case Helper.THEME_DARK: + setTheme(R.style.AppThemeDark_NoActionBar); + break; + case Helper.THEME_BLACK: + setTheme(R.style.AppThemeBlack_NoActionBar); + break; + default: + setTheme(R.style.AppThemeDark_NoActionBar); + } + setContentView(R.layout.activity_ower_charts); + + Toolbar toolbar = findViewById(R.id.toolbar); + if( theme == Helper.THEME_BLACK) + toolbar.setBackgroundColor(ContextCompat.getColor(OwnerChartsActivity.this, R.color.black)); + setSupportActionBar(toolbar); + + ActionBar actionBar = getSupportActionBar(); + if( actionBar != null ){ + LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + assert inflater != null; + @SuppressLint("InflateParams") View view = inflater.inflate(R.layout.simple_action_bar, null); + actionBar.setCustomView(view, new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); + + ImageView close_toot = actionBar.getCustomView().findViewById(R.id.close_toot); + close_toot.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + TextView toolbarTitle = actionBar.getCustomView().findViewById(R.id.toolbar_title); + ImageView pp_actionBar = actionBar.getCustomView().findViewById(R.id.pp_actionBar); + if (theme == Helper.THEME_LIGHT){ + Helper.colorizeToolbar(actionBar.getCustomView().findViewById(R.id.toolbar), R.color.black, OwnerChartsActivity.this); + } + toolbarTitle.setText(getString(R.string.owner_charts)); + } + + + chart = findViewById(R.id.chart); + settings_time_from = findViewById(R.id.settings_time_from); + settings_time_to = findViewById(R.id.settings_time_to); + if( theme == Helper.THEME_DARK){ + style = R.style.DialogDark; + }else if( theme == Helper.THEME_BLACK){ + style = R.style.DialogBlack; + }else { + style = R.style.Dialog; + } + + SQLiteDatabase db = Sqlite.getInstance(OwnerChartsActivity.this, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + dateIni = new StatusCacheDAO(OwnerChartsActivity.this, db).getSmallerDate(StatusCacheDAO.ARCHIVE_CACHE); + dateEnd = new StatusCacheDAO(OwnerChartsActivity.this, db).getGreaterDate(StatusCacheDAO.ARCHIVE_CACHE); + if(dateIni == null){ + dateIni = new Date(); + } + if( dateEnd == null){ + dateEnd = new Date(); + } + + String dateInitString = Helper.shortDateToString(dateIni); + String dateEndString = Helper.shortDateToString(dateEnd); + + settings_time_from.setText(dateInitString); + settings_time_to.setText(dateEndString); + + new RetrieveChartsAsyncTask(OwnerChartsActivity.this, dateIni, dateEnd, OwnerChartsActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + + } + + + private DatePickerDialog.OnDateSetListener iniDateSetListener = + new DatePickerDialog.OnDateSetListener() { + + public void onDateSet(DatePicker view, int year, + int monthOfYear, int dayOfMonth) { + Calendar c = Calendar.getInstance(); + c.set(year, monthOfYear, dayOfMonth, 0, 0); + dateIni = new Date(c.getTimeInMillis()); + settings_time_from.setText(Helper.shortDateToString(new Date(c.getTimeInMillis()))); + } + + }; + private DatePickerDialog.OnDateSetListener endDateSetListener = + new DatePickerDialog.OnDateSetListener() { + + public void onDateSet(DatePicker view, int year, + int monthOfYear, int dayOfMonth) { + Calendar c = Calendar.getInstance(); + c.set(year, monthOfYear, dayOfMonth, 23, 59); + + dateEnd = new Date(c.getTimeInMillis()); + settings_time_to.setText(Helper.shortDateToString(new Date(c.getTimeInMillis()))); + } + + }; + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + + + @Override + public void onDestroy() { + super.onDestroy(); + } + + + @Override + public void onCharts(Charts charts) { + + } +} diff --git a/app/src/main/java/app/fedilab/android/asynctasks/RetrieveChartsAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/RetrieveChartsAsyncTask.java new file mode 100644 index 000000000..fdcd23062 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/asynctasks/RetrieveChartsAsyncTask.java @@ -0,0 +1,66 @@ +/* Copyright 2019 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ +package app.fedilab.android.asynctasks; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; + +import java.lang.ref.WeakReference; +import java.util.Date; + +import app.fedilab.android.client.Entities.Charts; +import app.fedilab.android.interfaces.OnRetrieveChartsInterface; +import app.fedilab.android.sqlite.Sqlite; +import app.fedilab.android.sqlite.StatusCacheDAO; + + +/** + * Created by Thomas on 28/07/2019. + * Creates charts for an account + */ + +public class RetrieveChartsAsyncTask extends AsyncTask { + + + private OnRetrieveChartsInterface listener; + private WeakReference contextReference; + private Charts charts; + private Date dateIni; + private Date dateEnd; + + public RetrieveChartsAsyncTask(Context context, Date dateIni, Date dateEnd, OnRetrieveChartsInterface onRetrieveChartsInterface){ + this.contextReference = new WeakReference<>(context); + this.listener = onRetrieveChartsInterface; + this.dateIni = dateIni; + this.dateEnd = dateEnd; + } + + + @Override + protected Void doInBackground(Void... params) { + + SQLiteDatabase db = Sqlite.getInstance(contextReference.get(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + charts = new StatusCacheDAO(contextReference.get(), db).getCharts(dateIni, dateEnd); + + return null; + } + + @Override + protected void onPostExecute(Void result) { + listener.onCharts(charts); + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Charts.java b/app/src/main/java/app/fedilab/android/client/Entities/Charts.java new file mode 100644 index 000000000..ffb400455 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/Entities/Charts.java @@ -0,0 +1,76 @@ +package app.fedilab.android.client.Entities; + +import java.util.List; + +/* Copyright 2019 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +public class Charts { + + private List xLabels; + private List yLabels; + private List xValues; + private List statuses; + private List boosts; + private List replies; + + public List getxLabels() { + return xLabels; + } + + public void setxLabels(List xLabels) { + this.xLabels = xLabels; + } + + public List getyLabels() { + return yLabels; + } + + public void setyLabels(List yLabels) { + this.yLabels = yLabels; + } + + public List getxValues() { + return xValues; + } + + public void setxValues(List xValues) { + this.xValues = xValues; + } + + public List getStatuses() { + return statuses; + } + + public void setStatuses(List statuses) { + this.statuses = statuses; + } + + public List getBoosts() { + return boosts; + } + + public void setBoosts(List boosts) { + this.boosts = boosts; + } + + public List getReplies() { + return replies; + } + + public void setReplies(List replies) { + this.replies = replies; + } +} diff --git a/app/src/main/java/app/fedilab/android/interfaces/OnRetrieveChartsInterface.java b/app/src/main/java/app/fedilab/android/interfaces/OnRetrieveChartsInterface.java new file mode 100644 index 000000000..b41b06168 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/interfaces/OnRetrieveChartsInterface.java @@ -0,0 +1,25 @@ +/* Copyright 2019 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ +package app.fedilab.android.interfaces; + +import app.fedilab.android.client.Entities.Charts; + +/** + * Created by Thomas on 28/07/2019. + * Interface when retrieving charts + */ +public interface OnRetrieveChartsInterface { + void onCharts(Charts charts); +} diff --git a/app/src/main/java/app/fedilab/android/sqlite/StatusCacheDAO.java b/app/src/main/java/app/fedilab/android/sqlite/StatusCacheDAO.java index c33414ccd..1436f709a 100644 --- a/app/src/main/java/app/fedilab/android/sqlite/StatusCacheDAO.java +++ b/app/src/main/java/app/fedilab/android/sqlite/StatusCacheDAO.java @@ -20,12 +20,15 @@ import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import java.time.LocalDate; import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; +import app.fedilab.android.client.Entities.Charts; import app.fedilab.android.client.Entities.Statistics; import app.fedilab.android.client.Entities.Status; import app.fedilab.android.client.Entities.Tag; @@ -434,6 +437,80 @@ public class StatusCacheDAO { } + + public Charts getCharts(Date dateIni, Date dateEnd){ + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + String instance = Helper.getLiveInstance(context); + Charts charts = new Charts(); + dateIni = new Date(dateIni.getYear(), dateIni.getMonth(), dateIni.getDay(), 0, 0, 0); + + Calendar start = Calendar.getInstance(); + start.setTime(dateIni); + start.set(Calendar.HOUR,0); + start.set(Calendar.MINUTE,0); + start.set(Calendar.SECOND,0); + + Calendar end = Calendar.getInstance(); + end.setTime(dateEnd); + end.set(Calendar.HOUR,23); + end.set(Calendar.MINUTE,59); + end.set(Calendar.SECOND,59); + + StringBuilder selection = new StringBuilder(Sqlite.COL_CACHED_ACTION + " = '" + ARCHIVE_CACHE + "' AND " + Sqlite.COL_INSTANCE + " = '" + instance + "' AND " + Sqlite.COL_USER_ID + " = '" + userId + "'"); + selection.append(" AND " + Sqlite.COL_CREATED_AT + " >= '").append(Helper.dateToString(start.getTime())).append("'"); + selection.append(" AND " + Sqlite.COL_CREATED_AT + " <= '").append(Helper.dateToString(end.getTime())).append("'"); + + List data = new ArrayList<>(); + try { + Cursor c = db.query(Sqlite.TABLE_STATUSES_CACHE, null, selection.toString(), null, null, null, Sqlite.COL_CREATED_AT + " ASC"); + data = cursorToListStatuses(c); + } catch (Exception e) { + e.printStackTrace(); + } + int inc = 0; + List xLabel = new ArrayList<>(); + List xValues = new ArrayList<>(); + List statuses = new ArrayList<>(); + List boosts = new ArrayList<>(); + List replies = new ArrayList<>(); + if( data != null && data.size() > 0) { + while (!start.after(end)) { + Date targetDay = start.getTime(); + Date dateLimite = new Date(targetDay.getTime() - TimeUnit.DAYS.toMillis(1)); + xLabel.add(Helper.shortDateToString(targetDay)); + xValues.add(inc); + int boostsCount = 0; + int repliesCount = 0; + int statusesCount = 0; + for(Status status: data){ + if(status.getCreated_at().after(targetDay) && status.getCreated_at().before(dateLimite)){ + if( status.getReblog() != null){ + boostsCount++; + }else if( status.getIn_reply_to_id() != null){ + repliesCount++; + }else { + statusesCount++; + } + }else{ + inc++; + break; + } + } + boosts.add(boostsCount); + replies.add(repliesCount); + statuses.add(statusesCount); + start.add(Calendar.DATE, 1); + } + } + charts.setxLabels(xLabel); + charts.setxValues(xValues); + charts.setBoosts(boosts); + charts.setReplies(replies); + charts.setStatuses(statuses); + return charts; + } + /** * Returns a cached status by id in db * @return stored status StoredStatus diff --git a/app/src/main/res/layout/activity_ower_charts.xml b/app/src/main/res/layout/activity_ower_charts.xml new file mode 100644 index 000000000..7afa8a8b2 --- /dev/null +++ b/app/src/main/res/layout/activity_ower_charts.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + +