diff --git a/app/build.gradle b/app/build.gradle index 5ddcf21..0a28739 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,27 +24,30 @@ android { } dependencies { - 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' + // androidx + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.cardview:cardview:1.0.0' - - implementation fileTree(dir: 'libs', include: ['*.jar']) - - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.navigation:navigation-fragment:2.3.0' implementation 'androidx.navigation:navigation-ui:2.3.0' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.preference:preference:1.1.1' + implementation "androidx.webkit:webkit:1.3.0" - // jsoup HTML parser library @ https://jsoup.org/ + // JSON save/restore shared preference + implementation 'com.google.code.gson:gson:2.8.5' + + // Theme + implementation 'com.google.android.material:material:1.2.1' + + // Scraping implementation 'org.jsoup:jsoup:1.13.1' + // Image loading and transforming implementation 'com.squareup.picasso:picasso:2.71828' implementation 'jp.wasabeef:picasso-transformations:2.2.1' - implementation 'androidx.preference:preference:1.1.1' - - implementation "androidx.webkit:webkit:1.3.0" - + // tests testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' diff --git a/app/src/main/java/com/akdev/nofbeventscraper/FbScraper.java b/app/src/main/java/com/akdev/nofbeventscraper/FbScraper.java index 8ea9f16..3b985cc 100644 --- a/app/src/main/java/com/akdev/nofbeventscraper/FbScraper.java +++ b/app/src/main/java/com/akdev/nofbeventscraper/FbScraper.java @@ -200,7 +200,6 @@ public class FbScraper extends AsyncTask { Document document = Jsoup.connect(url).userAgent(user_agent).get(); if (document == null) { - throw new IOException(); } String json = document .select("script[type = application/ld+json]") @@ -228,10 +227,6 @@ public class FbScraper extends AsyncTask { FbEvent event = new FbEvent(url, name, start_date, end_date, description, location, image_url); this.events.add(event); - this.events.add(event); - this.events.add(new FbEvent()); - this.events.add(event); - this.events.add(event); } catch (URISyntaxException | MalformedURLException e) { e.printStackTrace(); @@ -266,10 +261,9 @@ public class FbScraper extends AsyncTask { if (main != null) { if (! this.events.isEmpty()) { - main.get().update(events); + main.get().addEvents(this.events); } else { main.get().error(error); - main.get().clear(false); } } } diff --git a/app/src/main/java/com/akdev/nofbeventscraper/MainActivity.java b/app/src/main/java/com/akdev/nofbeventscraper/MainActivity.java index 9d37657..2d73cf2 100644 --- a/app/src/main/java/com/akdev/nofbeventscraper/MainActivity.java +++ b/app/src/main/java/com/akdev/nofbeventscraper/MainActivity.java @@ -4,18 +4,19 @@ import android.annotation.SuppressLint; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; -import android.text.Layout; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.ImageView; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.menu.MenuBuilder; import androidx.appcompat.widget.Toolbar; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -24,9 +25,11 @@ import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputLayout; -import com.squareup.picasso.Picasso; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import java.lang.ref.WeakReference; +import java.lang.reflect.Type; import java.util.List; import java.util.Objects; @@ -46,23 +49,48 @@ public class MainActivity extends AppCompatActivity { EventAdapter adapter; LinearLayoutManager linear_layout_manager; - @Override - public void onRestoreInstanceState(Bundle state) { - super.onRestoreInstanceState(state); + private List getSavedEvents() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - if (!state.getBoolean("events_empty")) { - startScraping(); + Gson gson = new Gson(); + String json = prefs.getString("events", ""); + + Type event_list_type = new TypeToken>() { + }.getType(); + List list = gson.fromJson(json, event_list_type); + + if (list == null) { + list = createEventList(); } + return list; } + /** + * Callback after clearing events from settings needed. + */ @Override - public void onSaveInstanceState(Bundle state) { + public void onRestart() { + super.onRestart(); + + events.clear(); + events.addAll(getSavedEvents()); + adapter.notifyDataSetChanged(); + } + + /** + * Save events list to SharedPreferences as JSON + */ + @Override + public void onSaveInstanceState(@NonNull Bundle state) { super.onSaveInstanceState(state); - state.putBoolean("events_empty", events.isEmpty()); - - + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences.Editor prefs_edit = prefs.edit(); + Gson gson = new Gson(); + String json = gson.toJson(events); + prefs_edit.putString("events", json); + prefs_edit.apply(); } @Override @@ -78,11 +106,11 @@ public class MainActivity extends AppCompatActivity { paste_button = findViewById(R.id.paste_button); /* - * initialize recycler view with empty list of events + * initialize recycler view with saved list of events * scroll horizontal with snapping */ RecyclerView recycler_view = findViewById(R.id.recycler_view); - events = createEventList(); + this.events = getSavedEvents(); adapter = new EventAdapter(events); recycler_view.setAdapter(adapter); linear_layout_manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); @@ -94,7 +122,6 @@ public class MainActivity extends AppCompatActivity { * Display title only when toolbar is collapsed */ AppBarLayout app_bar_layout = findViewById(R.id.app_bar); - app_bar_layout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { boolean show = true; int scroll_range = -1; @@ -114,6 +141,7 @@ public class MainActivity extends AppCompatActivity { } } }); + /* * Paste button: get last entry from clipboard */ @@ -125,7 +153,6 @@ public class MainActivity extends AppCompatActivity { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); String str = clipboard.getPrimaryClip().getItemAt(0).getText().toString(); - clear(true); edit_text_uri_input.setText(str); startScraping(); @@ -137,16 +164,15 @@ public class MainActivity extends AppCompatActivity { }); /* - * Clear button: delete all events + * Error in input: clear input on click */ - View.OnClickListener listener = new View.OnClickListener() { + layout_uri_input.setErrorIconOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - clear(true); + layout_uri_input.setError(null); + edit_text_uri_input.setText(null); } - }; - layout_uri_input.setEndIconOnClickListener(listener); - layout_uri_input.setErrorIconOnClickListener(listener); + }); /* @@ -163,7 +189,6 @@ public class MainActivity extends AppCompatActivity { } }); - /* * Get data from intent: if launched by other application * via "share to" or "open with" @@ -181,10 +206,6 @@ public class MainActivity extends AppCompatActivity { edit_text_uri_input.setText(shared_text); startScraping(); } - - - - } /** @@ -208,39 +229,16 @@ public class MainActivity extends AppCompatActivity { } /** - * Clears all event text field strings and errors and also the input field depending if wanted. - * Loads the default banner into the toolbar image view and disables unneeded buttons. + * Adds new events to the start of the events list. * - * @param clear_uri Choose whether to clear the input uri field, too + * @param new_events the list of events that was scraped by FbScraper */ - public void clear(boolean clear_uri) { + public void addEvents(List new_events) { - if (clear_uri) { - edit_text_uri_input.setText(""); - layout_uri_input.setError(null); + if (new_events != null) { + this.events.addAll(0, new_events); + this.adapter.notifyDataSetChanged(); } - - if (scraper != null) { - scraper.cancel(true); - scraper = null; - } - - this.events.clear(); - adapter.notifyDataSetChanged(); - } - - /** - * Updates the text fields with the event information provided. - * If something is missing, the corresponding test field will show an error. - * - * @param events the event information that was scraped by FbScraper - */ - public void update(List events) { - - this.events.clear(); - this.events.addAll(events); - - adapter.notifyDataSetChanged(); } @SuppressLint("RestrictedApi") @@ -259,12 +257,9 @@ public class MainActivity extends AppCompatActivity { @Override public boolean onOptionsItemSelected(MenuItem item) { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); - //noinspection SimplifiableIfStatement if (id == R.id.action_about) { startActivity(new Intent(this, AboutActivity.class)); return true; diff --git a/app/src/main/java/com/akdev/nofbeventscraper/SettingsActivity.java b/app/src/main/java/com/akdev/nofbeventscraper/SettingsActivity.java index 9abe29a..6247803 100644 --- a/app/src/main/java/com/akdev/nofbeventscraper/SettingsActivity.java +++ b/app/src/main/java/com/akdev/nofbeventscraper/SettingsActivity.java @@ -1,10 +1,20 @@ package com.akdev.nofbeventscraper; +import android.content.SharedPreferences; +import android.graphics.Color; import android.os.Bundle; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; + +import com.google.android.material.snackbar.Snackbar; +import com.google.gson.Gson; public class SettingsActivity extends AppCompatActivity { @@ -26,6 +36,25 @@ public class SettingsActivity extends AppCompatActivity { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.root_preferences, rootKey); + + Preference button = findPreference("event_reset"); + if (button != null) { + button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + + SharedPreferences prefs = preference.getSharedPreferences(); + + prefs.edit().remove("events").apply(); + + Snackbar.make(getActivity().findViewById(android.R.id.content), + getString(R.string.preferences_event_snackbar), Snackbar.LENGTH_SHORT).show(); + + return true; + } + }); + } + } } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a7265ca..53bc4f7 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -33,7 +33,7 @@ android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/ic_banner_foreground" - app:layout_collapseMode="parallax" /> + app:layout_collapseMode="pin" /> + android:orientation="vertical" + android:layout_marginTop="16dp"> diff --git a/app/src/main/res/layout/item_event.xml b/app/src/main/res/layout/item_event.xml index ef96c46..3cae5fe 100644 --- a/app/src/main/res/layout/item_event.xml +++ b/app/src/main/res/layout/item_event.xml @@ -33,7 +33,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/error_no_name" + android:text="@string/event_placeholder" android:textAppearance="?attr/textAppearanceHeadline6" /> @@ -60,7 +60,7 @@ android:id="@+id/text_view_event_location" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/error_no_location" + android:text="@string/event_placeholder" android:textAppearance="?attr/textAppearanceBody1" /> @@ -92,14 +92,14 @@ android:id="@+id/text_view_event_start" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/error_no_start_date" + android:text="@string/event_placeholder" android:textAppearance="?attr/textAppearanceBody2" /> @@ -113,7 +113,7 @@ android:layout_margin="8dp" android:ellipsize="end" android:maxLines="5" - android:text="abc\nabc\nabc\nabc\nabc\nabc\nabc\n" + android:text="@string/event_placeholder" android:textAppearance="?attr/textAppearanceBody2" android:textColor="?android:attr/textColorSecondary" /> diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 388a06a..376e05d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -5,24 +5,17 @@ Hilfe Einstellungen Veranstaltungslink - Veranstaltungsname - Veranstaltungsbeginn - Veranstantungsende - Veranstaltungsort - Veranstaltungsbeschreibung Facebook-Link zur Veranstaltung einfügen Zum Kalender hinzufügen Einfügen von Inhalten aus der Zwischenablage in das URL-Eingabefeld Welcher URL-Präfix ist zu verwenden? Die Nutzung von m.facebook.com ist stabiler und schneller. Die Verwendung von www.facebook.com funktioniert besser bei Ereignissen mit mehreren Instanzen und zeigt eine hochauflösende Vorschau an, geht aber irgendwann kaputt, wenn Facebook das klassische Design deaktiviert. Fehler: Zwischenablage leer - Fehler: Veranstaltungsname nicht verfügbar - Fehler: Veranstaltungsbeginn nicht verfügbar - Fehler: Veranstaltungsende nicht verfügbar - Fehler: Veranstaltungsort nicht verfügbar - Fehler: Veranstaltungsbeschreibung nicht verfügbar Fehler: Veranstaltungsdaten nicht gefunden Fehler: URL ungültig Fehler: Keine Verbindung möglich Fehler: Unbekannter Fehler + Veranstaltungen + Veranstaltungsliste löschen + "Veranstaltungen gelöscht " \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f276784..ac07236 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,11 +8,6 @@ Event link - Event name - Event start - Event end - Event location - Event description Paste Facebook link to the event @@ -21,11 +16,6 @@ Error: clipboard empty - Error: Event name unavailable - Error: Event start date unavailable - Error: Event end date unavailable - Error: Event location unavailable - Error: Event description unavailable Error: Scraping event data failed Error: URL invalid Error: Unable to connect @@ -33,10 +23,12 @@ Scraper - Which URL prefix to use "Using m.facebook.com is more stable and faster. Using www.facebook.com works better with multiple instance events and will display a high resolution preview but will eventually break when Facebook disables the classic design. " - + Events + Clear event list + Events list cleared + Placeholder diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 8ae7a54..4f0f632 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -5,17 +5,23 @@ + app:title="@string/preferences_url_setting" /> + + + + + - \ No newline at end of file