1
0
mirror of https://github.com/akaessens/NoFbEventScraper synced 2025-06-05 23:29:13 +02:00

much refactoring:

-move event formatting logic to event class
-disable editing of event output, it's available in the calendar app
-replace string datetimes with ZonedDateZime
-move uri checking logic to scraper
-update exception handling and error messages
-reformatting and renaming
-fix messy xml layouts
-update tests
-add comments
This commit is contained in:
akaessens
2020-08-28 14:31:45 +02:00
parent 05f3ba9a33
commit 16d390094e
8 changed files with 427 additions and 568 deletions

View File

@ -40,6 +40,6 @@ dependencies {
implementation 'com.squareup.picasso:picasso:2.71828' implementation 'com.squareup.picasso:picasso:2.71828'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
} }

View File

@ -1,110 +0,0 @@
package com.akdev.nofbeventscraper;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class MainActivityUnitTest {
@Test
public void TestSubdomainUrl() {
Instrumentation mInstrumentation = getInstrumentation();
// We register our interest in the activity
Instrumentation.ActivityMonitor monitor = mInstrumentation.addMonitor(MainActivity.class.getName(), null, false);
// We launch it
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(mInstrumentation.getTargetContext(), MainActivity.class.getName());
mInstrumentation.startActivitySync(intent);
MainActivity mainActivity = (MainActivity) getInstrumentation().waitForMonitor(monitor);
// We register our interest in the next activity from the sequence in this use case
mInstrumentation.removeMonitor(monitor);
final String exp = "https://m.facebook.com/events/261145401687844";
String url = "https://www.facebook.com/events/261145401687844";
String act = mainActivity.checkURI(url);
assertEquals(exp, act);
url = "https://de-de.facebook.com/events/261145401687844";
act = mainActivity.checkURI(url);
assertEquals(exp, act);
url = "https://m.facebook.com/events/261145401687844";
act = mainActivity.checkURI(url);
assertEquals(exp, act);
url = "https://www.facebook.com/events/261145401687844/?active_tab=discussion";
act = mainActivity.checkURI(url);
assertEquals(exp, act);
url = "https://www.facebook.com/events/261145401687844?reflink_something";
act = mainActivity.checkURI(url);
assertEquals(exp, act);
}
@Test
public void TestTimeToEpoch() {
Instrumentation mInstrumentation = getInstrumentation();
// We register our interest in the activity
Instrumentation.ActivityMonitor monitor = mInstrumentation.addMonitor(MainActivity.class.getName(), null, false);
// We launch it
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(mInstrumentation.getTargetContext(), MainActivity.class.getName());
mInstrumentation.startActivitySync(intent);
MainActivity mainActivity = (MainActivity) getInstrumentation().waitForMonitor(monitor);
// We register our interest in the next activity from the sequence in this use case
mInstrumentation.removeMonitor(monitor);
String in = "2020-07-29T12:00:00+00:00";
Long exp = new Long(1596024000);
exp = exp* 1000;
Long act = mainActivity.convertTimeToEpoch(in);
assertEquals(exp, act);
in = "2020-07-29T12:00:00+02:00";
exp = new Long(1596016800);
exp = exp* 1000;
act = mainActivity.convertTimeToEpoch(in);
assertEquals(exp, act);
in = "1970-01-01T00:00:00+00:00";
exp = new Long(0);
exp = exp* 1000;
act = mainActivity.convertTimeToEpoch(in);
assertEquals(exp, act);
in = "1970-01-01T02:00:00+02:00";
exp = new Long(0);
exp = exp* 1000;
act = mainActivity.convertTimeToEpoch(in);
assertEquals(exp, act);
}
}

View File

@ -5,13 +5,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class ScraperUnitTest { public class ScraperUnitTest {
@Test @Test
public void TestLocation() { public void testLocation() {
FbScraper scraper = new FbScraper(null, ""); FbScraper scraper = new FbScraper(null, "");
@ -32,37 +38,50 @@ public class ScraperUnitTest {
} }
@Test @Test
public void TestTimezone() { public void testTimezone() {
FbScraper scraper = new FbScraper(null, ""); FbScraper scraper = new FbScraper(null, "");
String exp = "2020-10-23T05:00:00+02:00"; String exp = "2020-10-23T05:00+02:00";
String in = "2020-10-23T05:00:00+0200"; String in = "2020-10-23T05:00:00+0200";
String act = scraper.fixTimezone(in); String act = scraper.toZonedDateTime(in).toString();
assertEquals(exp, act); assertEquals(exp, act);
exp = ""; exp = null;
in = ""; in = "";
act = scraper.fixTimezone(in); ZonedDateTime act2 = scraper.toZonedDateTime(in);
assertEquals(exp, act); assertEquals(exp, act2);
} }
@Test @Test
public void TestLinks() { public void testDescriptionLinks() {
FbScraper scraper = new FbScraper(null, ""); FbScraper scraper = new FbScraper(null, "");
String in = "foo @[152580919265:274:MagentaMusik 360] bar"; String in = "foo @[152580919265:274:MagentaMusik 360] bar";
String exp = "foo MagentaMusik 360 [m.facebook.com/152580919265] bar"; String exp = "foo MagentaMusik 360 [m.facebook.com/152580919265] bar";
String act = scraper.fixLinks(in); String act = scraper.fixDescriptionLinks(in);
assertEquals(exp, act); assertEquals(exp, act);
in = "foo @[152580919265:274:MagentaMusik 360] bar @[666666666666:274:NoOfTheBeast]"; in = "foo @[152580919265:274:MagentaMusik 360] bar @[666666666666:274:NoOfTheBeast]";
exp = "foo MagentaMusik 360 [m.facebook.com/152580919265] bar NoOfTheBeast [m.facebook.com/666666666666]"; exp = "foo MagentaMusik 360 [m.facebook.com/152580919265] bar NoOfTheBeast [m.facebook.com/666666666666]";
act = scraper.fixLinks(in); act = scraper.fixDescriptionLinks(in);
assertEquals(exp, act);
}
@Test
public void testURI() throws MalformedURLException, URISyntaxException {
FbScraper scraper = new FbScraper(null, "");
String in = "https://www.facebook.com/events/1234324522341432?refsomething";
String exp = "https://m.facebook.com/events/1234324522341432";
String act = scraper.fixURI(in);
assertEquals(exp, act); assertEquals(exp, act);
in = "https://de-de.facebook.com/events/1234324522341432/?active_tab=discussion";
act = scraper.fixURI(in);
assertEquals(exp, act);
} }
} }

View File

@ -1,14 +1,24 @@
package com.akdev.nofbeventscraper; package com.akdev.nofbeventscraper;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
public class FbEvent { public class FbEvent {
public String url;
public String name; public String name;
public String start_date; public ZonedDateTime start_date;
public String end_date; public ZonedDateTime end_date;
public String description; public String description;
public String location; public String location;
public String image_url; public String image_url;
public FbEvent (String name, String start_date, String end_date, String description, String location, String image_url) { public FbEvent() {
}
public FbEvent(String name, ZonedDateTime start_date, ZonedDateTime end_date, String description, String location, String image_url) {
this.name = name; this.name = name;
this.start_date = start_date; this.start_date = start_date;
this.end_date = end_date; this.end_date = end_date;
@ -16,4 +26,21 @@ public class FbEvent {
this.location = location; this.location = location;
this.image_url = image_url; this.image_url = image_url;
} }
static Long dateTimeToEpoch(ZonedDateTime datetime) {
try {
return datetime.toEpochSecond() * 1000;
} catch (Exception e) {
return null;
}
}
static String dateTimeToString(ZonedDateTime datetime) {
try {
return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).format(datetime);
} catch (Exception e) {
return "";
}
}
} }

View File

@ -1,6 +1,5 @@
package com.akdev.nofbeventscraper; package com.akdev.nofbeventscraper;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.text.Editable; import android.text.Editable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
@ -11,69 +10,100 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FbScraper extends AsyncTask<Void, Void, Void> { public class FbScraper extends AsyncTask<Void, Void, Void> {
private String url;
private String error; private String error;
private MainActivity main; private String input_str;
private WeakReference<MainActivity> main; // no context leak with WeakReference
private FbEvent event; private FbEvent event;
FbScraper(MainActivity main, String url) { FbScraper(WeakReference<MainActivity> main, String str) {
this.url = url;
this.main = main; this.main = main;
this.input_str = str;
}
protected String fixURI(String str) throws URISyntaxException, MalformedURLException {
// check for url format
new URL(str).toURI();
Pattern pattern = Pattern.compile("(facebook.com/events/[0-9]*)");
Matcher matcher = pattern.matcher(str);
if (matcher.find()) {
// rewrite url to m.facebook and dismiss any query strings or referrals
return "https://m." + matcher.group(1);
} else {
throw new URISyntaxException(str, "Does not contain event.");
}
} }
protected String fixLocation(String location_json) { protected String fixLocation(String location_json) {
String name = ""; String location_name = "";
try { try {
JSONObject reader = new JSONObject(location_json); JSONObject reader = new JSONObject(location_json);
name = reader.getString("name"); location_name = reader.getString("name");
JSONObject address = reader.getJSONObject("address"); JSONObject address = reader.getJSONObject("address");
String type = address.getString("@type"); String type = address.getString("@type");
if (type.equals("PostalAddress")) if (type.equals("PostalAddress")) {
{
String postal_code = address.getString("postalCode"); String postal_code = address.getString("postalCode");
String address_locality = address.getString("addressLocality"); String address_locality = address.getString("addressLocality");
String address_country = address.getString("addressCountry");
String street_address = address.getString("streetAddress"); String street_address = address.getString("streetAddress");
// included in locality
//String address_country = address.getString("addressCountry");
return name + ", " + street_address + ", " + postal_code + " " + address_locality;
}
else
{
return name;
}
return location_name + ", "
+ street_address + ", "
+ postal_code + " "
+ address_locality;
} else {
return location_name;
}
} catch (JSONException e) { } catch (JSONException e) {
e.printStackTrace(); e.printStackTrace();
return name; return location_name;
} }
} }
protected String fixTimezone(String time_in) { protected ZonedDateTime toZonedDateTime(String time_in) {
try { try {
// time in is missing a : in the timezone offset
Editable editable = new SpannableStringBuilder(time_in); Editable editable = new SpannableStringBuilder(time_in);
String time_str = editable.insert(22, ":").toString();
return editable.insert(22, ":").toString(); // parse e.g. 2011-12-03T10:15:30+01:00
return ZonedDateTime.parse(time_str, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return ""; return null;
} }
} }
protected String fixLinks(String description_in) { protected String fixDescriptionLinks(String description_in) {
try { try {
// @[152580919265:274:MagentaMusik 360] -> m.facebook.com/152580919265 /* @[152580919265:274:SiteDescription]
* to
* SiteDescription [m.facebook.com/152580919265] */
return description_in.replaceAll("@\\[([0-9]{10,}):[0-9]{3}:([^]]*)]", return description_in.replaceAll("@\\[([0-9]{10,}):[0-9]{3}:([^]]*)]",
"$2 [m.facebook.com/$1]"); "$2 [m.facebook.com/$1]");
@ -86,60 +116,45 @@ public class FbScraper extends AsyncTask<Void, Void, Void> {
private String readFromJson(JSONObject reader, String field) { private String readFromJson(JSONObject reader, String field) {
try { try {
return reader.getString(field); return reader.getString(field);
} } catch (Exception e) {
catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return ""; return "";
} }
} }
@Override @Override
protected Void doInBackground(Void... voids) { protected Void doInBackground(Void... voids) {
Document document = null;
try { try {
document = Jsoup.connect(url).userAgent("Mozilla").get(); String url = fixURI(input_str);
// useragent needed with Jsoup > 1.12
Document document = Jsoup.connect(url).userAgent("Mozilla").get();
String json = document
.select("script[type = application/ld+json]")
.first().data();
try { JSONObject reader = new JSONObject(json);
String json = document.select("script[type = application/ld+json]").first().data();
JSONObject reader = new JSONObject(json); event = new FbEvent();
event.url = url;
event.name = readFromJson(reader, "name");
event.start_date = toZonedDateTime(readFromJson(reader, "startDate"));
event.end_date = toZonedDateTime(readFromJson(reader, "endDate"));
event.description = fixDescriptionLinks(readFromJson(reader, "description"));
event.location = fixLocation(readFromJson(reader, "location"));
event.image_url = readFromJson(reader, "image");
String event_name = readFromJson(reader, "name"); } catch (URISyntaxException | MalformedURLException e) {
String event_start = fixTimezone(readFromJson(reader, "startDate"));
String event_end = fixTimezone(readFromJson(reader, "endDate"));
String event_description = fixLinks(readFromJson(reader, "description"));
String location = fixLocation(readFromJson(reader, "location"));
String image_url = "";
try {
image_url = readFromJson(reader, "image"); // get from json
// get from event header
image_url = document.getElementsByClass("scaledImageFitWidth").first().attr("src");
} catch (Exception e) {
e.printStackTrace();
this.error = "Error: no image found";
}
if (event_name == null) {
this.event = null;
throw new Exception();
} else {
this.event = new FbEvent(event_name, event_start, event_end, event_description, location, image_url);
//this.event = new FbEvent("", "", "", "", "", "");
}
} catch (Exception e) {
e.printStackTrace();
this.error = "Error: Scraping event data failed";
}
} catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
this.error = "Error: URL not available"; this.error = "Error: URL invalid.";
} catch (JSONException e) {
e.printStackTrace();
this.error = "Error: Scraping event data failed";
} catch (IOException e) {
e.printStackTrace();
this.error = "Error: Unable to connect.";
} }
return null; return null;
} }
@ -152,11 +167,10 @@ public class FbScraper extends AsyncTask<Void, Void, Void> {
super.onPostExecute(aVoid); super.onPostExecute(aVoid);
if (this.event != null) { if (this.event != null) {
this.main.update(event); main.get().update(event);
} } else {
else { main.get().error(error);
main.error(error); main.get().clear(false);
this.main.clear(false);
} }
} }
} }

View File

@ -5,12 +5,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.textfield.TextInputEditText;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.provider.CalendarContract; import android.provider.CalendarContract;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
@ -19,32 +13,39 @@ import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.net.URL; import java.lang.ref.WeakReference;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.akdev.nofbeventscraper.FbEvent.dateTimeToEpoch;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private Button paste_button; protected Button ok_button;
private Button ok_button; protected Button paste_button;
private TextInputEditText field_uri_input; protected TextInputEditText edit_text_uri_input;
private TextInputEditText field_event_name; protected TextInputEditText edit_text_event_name;
private TextInputEditText field_event_start; protected TextInputEditText edit_text_event_start;
private TextInputEditText field_event_end; protected TextInputEditText edit_text_event_end;
private TextInputEditText field_event_location; protected TextInputEditText edit_text_event_location;
private TextInputEditText field_event_description; protected TextInputEditText edit_text_event_description;
private ImageView toolbar_image_view;
private CollapsingToolbarLayout toolbar_layout; protected TextInputLayout layout_uri_input;
private TextInputLayout input_layout; protected TextInputLayout layout_event_location;
private TextInputLayout location_layout;
private FbScraper scraper; protected ImageView image_view_toolbar;
protected CollapsingToolbarLayout layout_toolbar;
protected FbScraper scraper;
protected FbEvent event;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -56,43 +57,52 @@ public class MainActivity extends AppCompatActivity {
ok_button = (Button) findViewById(R.id.ok_button); ok_button = (Button) findViewById(R.id.ok_button);
paste_button = (Button) findViewById(R.id.paste_button); paste_button = (Button) findViewById(R.id.paste_button);
field_uri_input = (TextInputEditText) findViewById(R.id.field_uri_input); edit_text_uri_input = (TextInputEditText) findViewById(R.id.edit_text_uri_input);
input_layout = (TextInputLayout) findViewById(R.id.textInputLayout); edit_text_event_name = (TextInputEditText) findViewById(R.id.edit_text_event_name);
location_layout = (TextInputLayout) findViewById(R.id.location_layout); edit_text_event_start = (TextInputEditText) findViewById(R.id.edit_text_event_start);
field_event_name = (TextInputEditText) findViewById(R.id.field_event_name); edit_text_event_end = (TextInputEditText) findViewById(R.id.edit_text_event_end);
field_event_start = (TextInputEditText) findViewById(R.id.field_event_start); edit_text_event_location = (TextInputEditText) findViewById(R.id.edit_text_event_location);
field_event_end = (TextInputEditText) findViewById(R.id.field_event_end); edit_text_event_description = (TextInputEditText) findViewById(R.id.edit_text_event_description);
field_event_location = (TextInputEditText) findViewById(R.id.field_event_location);
field_event_description = (TextInputEditText) findViewById(R.id.field_event_description); layout_uri_input = (TextInputLayout) findViewById(R.id.layout_uri_input);
toolbar_image_view = (ImageView) findViewById(R.id.image_view); layout_event_location = (TextInputLayout) findViewById(R.id.layout_event_location);
toolbar_layout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout); layout_toolbar = (CollapsingToolbarLayout) findViewById(R.id.layout_toolbar);
image_view_toolbar = (ImageView) findViewById(R.id.image_view);
/*
* Default view settings
*/
ok_button.setEnabled(false); ok_button.setEnabled(false);
location_layout.setEndIconVisible(false); layout_event_location.setEndIconVisible(false);
image_view_toolbar.setImageResource(R.drawable.ic_banner_foreground);
toolbar_image_view.setImageResource(R.drawable.ic_banner_foreground);
/*
* Display title only when toolbar is collapsed
*/
AppBarLayout app_bar_layout = (AppBarLayout) findViewById(R.id.app_bar); AppBarLayout app_bar_layout = (AppBarLayout) findViewById(R.id.app_bar);
app_bar_layout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { app_bar_layout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
boolean isShow = true; boolean show = true;
int scrollRange = -1; int scroll_range = -1;
@Override @Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { public void onOffsetChanged(AppBarLayout app_bar_layout, int vertical_offset) {
if (scrollRange == -1) { if (scroll_range == -1) {
scrollRange = appBarLayout.getTotalScrollRange(); scroll_range = app_bar_layout.getTotalScrollRange();
} }
if (scrollRange + verticalOffset == 0) { if (scroll_range + vertical_offset == 0) {
toolbar_layout.setTitle(getString(R.string.app_name)); layout_toolbar.setTitle(getString(R.string.app_name));
isShow = true; show = true;
} else if(isShow) { } else if (show) {
toolbar_layout.setTitle(" "); layout_toolbar.setTitle(" ");
isShow = false; show = false;
} }
} }
}); });
/*
* Paste button: get last entry from clipboard
*/
paste_button.setOnClickListener(new View.OnClickListener() { paste_button.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
@ -102,9 +112,8 @@ public class MainActivity extends AppCompatActivity {
String str = clipboard.getPrimaryClip().getItemAt(0).getText().toString(); String str = clipboard.getPrimaryClip().getItemAt(0).getText().toString();
clear(true); clear(true);
field_uri_input.setText(str); edit_text_uri_input.setText(str);
} } catch (NullPointerException e) {
catch (NullPointerException e) {
e.printStackTrace(); e.printStackTrace();
error("Error: Clipboard empty"); error("Error: Clipboard empty");
} }
@ -112,71 +121,65 @@ public class MainActivity extends AppCompatActivity {
} }
}); });
/*
input_layout.setEndIconOnClickListener(new View.OnClickListener() { * Clear button: delete all text in all fields
*/
View.OnClickListener listener = new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
clear(true); clear(true);
} }
}); };
input_layout.setErrorIconOnClickListener(new View.OnClickListener() { layout_uri_input.setEndIconOnClickListener(listener);
layout_uri_input.setErrorIconOnClickListener(listener);
/*
* Maps button: launch maps intent
*/
layout_event_location.setEndIconOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
clear(true); String map_search = "geo:0,0?q=" + edit_text_event_location.getText();
}
});
Uri intent_uri = Uri.parse(map_search);
location_layout.setEndIconOnClickListener(new View.OnClickListener() { Intent map_intent = new Intent(Intent.ACTION_VIEW, intent_uri);
@Override if (map_intent.resolveActivity(getPackageManager()) != null) {
public void onClick(View view) { startActivity(map_intent);
String map_search = "geo:0,0?q=" + field_event_location.getText();
Uri gmmIntentUri = Uri.parse(map_search);
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
//mapIntent.setPackage("com.google.android.apps.maps");
if (mapIntent.resolveActivity(getPackageManager()) != null) {
startActivity(mapIntent);
} }
} }
}); });
/*
* Add to calendar button: launch calendar application
*/
ok_button.setOnClickListener(new View.OnClickListener() { ok_button.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
try {
Long start_epoch = convertTimeToEpoch(field_event_start.getText().toString());
Long end_epoch = convertTimeToEpoch(field_event_end.getText().toString());
String name = parseField(field_event_name); Long start_epoch = dateTimeToEpoch(event.start_date);
Long end_epoch = dateTimeToEpoch(event.end_date);
String location = parseField(field_event_location); Intent intent = new Intent(Intent.ACTION_EDIT);
String description = parseField(field_event_description); intent.setType("vnd.android.cursor.item/event");
String uri = parseField(field_uri_input); intent.putExtra(CalendarContract.Events.TITLE, event.name);
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, start_epoch);
Intent intent = new Intent(Intent.ACTION_EDIT); intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end_epoch);
intent.setType("vnd.android.cursor.item/event"); intent.putExtra(CalendarContract.Events.EVENT_LOCATION, event.location);
intent.putExtra(CalendarContract.Events.TITLE, name);
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, start_epoch);
intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end_epoch);
intent.putExtra(CalendarContract.Events.EVENT_LOCATION, location);
intent.putExtra(CalendarContract.Events.DESCRIPTION, uri + "\n\n" + description);
startActivity(intent);
}
catch (Exception e )
{
e.printStackTrace();
}
// prepend url in description
String desc = event.url + "\n\n" + event.description;
intent.putExtra(CalendarContract.Events.DESCRIPTION, desc);
startActivity(intent);
} }
}); });
field_uri_input.setOnKeyListener(new View.OnKeyListener() { /*
public boolean onKey(View view, int keyCode, KeyEvent keyevent) { * Enter button in uri input: start scraping
//If the keyevent is a key-down event on the "enter" button */
if ((keyevent.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { edit_text_uri_input.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View view, int keycode, KeyEvent keyevent) {
//If the key event is a key-down event on the "enter" button
if ((keyevent.getAction() == KeyEvent.ACTION_DOWN) && (keycode == KeyEvent.KEYCODE_ENTER)) {
startScraping(); startScraping();
return true; return true;
} }
@ -184,175 +187,122 @@ public class MainActivity extends AppCompatActivity {
} }
}); });
//get data from deep link
/*
* Get data from intent: if launched by other application
* via "share to" or "open with"
*/
Intent intent = getIntent(); Intent intent = getIntent();
Uri data = intent.getData(); Uri data = intent.getData();
String shared_text = intent.getStringExtra(Intent.EXTRA_TEXT); String shared_text = intent.getStringExtra(Intent.EXTRA_TEXT);
if (data != null) { if (data != null) {
// opening external fb link // opening external fb link
field_uri_input.setText(data.toString()); edit_text_uri_input.setText(data.toString());
startScraping(); startScraping();
} } else if (shared_text != null) {
else if (shared_text != null) {
//share to nofb //share to nofb
field_uri_input.setText(shared_text); edit_text_uri_input.setText(shared_text);
startScraping(); startScraping();
} }
} }
private String parseField(TextInputEditText field) {
try {
return field.getText().toString();
}
catch (Exception e) {
return null;
}
}
Long convertTimeToEpoch(String time_str) {
try {
ZonedDateTime datetime = ZonedDateTime.parse(time_str, DateTimeFormatter.ISO_DATE_TIME);
return datetime.toEpochSecond() * 1000;
} catch (Exception e)
{
e.printStackTrace();
}
return null;
}
String checkURI(String str)
{
try {
// check for a valid uri
new URL(str).toURI();
Pattern pattern = Pattern.compile("(facebook.com/events/[0-9]*)");
Matcher matcher = pattern.matcher(str);
if (matcher.find())
{
return "https://m." + matcher.group(1);
}
else
{
error("Error: Invalid URL");
clear(false);
return "";
}
}
catch (Exception e) {
e.printStackTrace();
error("Error: Invalid URL");
clear(false);
return "";
}
}
public void startScraping() { public void startScraping() {
error(null); error(null);
try { try {
String str = checkURI(field_uri_input.getText().toString()); String url = edit_text_uri_input.getText().toString();
scraper = new FbScraper(new WeakReference<>(this), url);
scraper.execute();
} catch (Exception e) {
if (!str.equals(""))
{
field_uri_input.setText(str);
scraper = new FbScraper(this, field_uri_input.getText().toString());
scraper.execute();
}
}
catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
error("Error: Invalid URL"); error("Error: Invalid URL");
} }
} }
public void error(String str) { public void error(String str) {
//Toast.makeText(this, str, Toast.LENGTH_SHORT).show(); layout_uri_input.setError(str);
input_layout.setError(str);
}
public void clear(boolean clearUri) {
if (clearUri) {
field_uri_input.setText("");
}
field_event_name.setText("");
field_event_name.setError(null);
field_event_start.setText("");
field_event_start.setError(null);
field_event_end.setText("");
field_event_end.setError(null);
field_event_location.setText("");
field_event_location.setError(null);
field_event_description.setText("");
field_event_description.setError(null);
toolbar_image_view.setImageDrawable(null);
if (scraper!=null)
{
scraper.cancel(true);
scraper = null;
}
ok_button.setEnabled(false);
location_layout.setEndIconVisible(false);
toolbar_image_view.setImageResource(R.drawable.ic_banner_foreground);
} }
public void update(FbEvent event) { public void clear(boolean clear_uri) {
field_event_name.setText(event.name);
input_layout.setError(null);
if (event.name.equals("")) if (clear_uri) {
{ edit_text_uri_input.setText("");
field_event_name.setError("no event name detected"); layout_uri_input.setError(null);
}
field_event_start.setText(event.start_date);
if (event.start_date.equals(""))
{
field_event_start.setError("no event start date detected");
}
field_event_end.setText(event.end_date);
if (event.end_date.equals(""))
{
field_event_end.setError("no event end date detected");
}
field_event_location.setText(event.location);
if (event.location.equals(""))
{
field_event_location.setError("no event location detected");
}
else
{
location_layout.setEndIconVisible(true);
field_event_location.setError(null);
}
field_event_description.setText(event.description);
if (event.description.equals(""))
{
field_event_description.setError("no event description detected");
} }
edit_text_event_name.setText("");
edit_text_event_start.setText("");
edit_text_event_end.setText("");
edit_text_event_location.setText("");
edit_text_event_description.setText("");
edit_text_event_name.setError(null);
// edit_text_event_start.setError(null);
edit_text_event_end.setError(null);
edit_text_event_location.setError(null);
edit_text_event_description.setError(null);
try { try {
Picasso.get().load(event.image_url).into(toolbar_image_view); scraper.cancel(true);
} catch (Exception e) scraper = null;
{ } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
ok_button.setEnabled(false);
layout_event_location.setEndIconVisible(false);
image_view_toolbar.setImageResource(R.drawable.ic_banner_foreground);
}
public void update(FbEvent scraped_event) {
this.event = scraped_event;
edit_text_uri_input.setText(event.url);
if (event.name.equals("")) {
edit_text_event_name.setError("no event name detected");
} else {
edit_text_event_name.setText(event.name);
}
if (event.start_date == null) {
edit_text_event_start.setError("no event start date detected");
} else {
String str = FbEvent.dateTimeToString(event.start_date);
edit_text_event_start.setText(str);
}
if (event.end_date == null) {
edit_text_event_end.setError("no event end date detected");
} else {
String str = FbEvent.dateTimeToString(event.end_date);
edit_text_event_end.setText(str);
}
if (event.location.equals("")) {
edit_text_event_location.setError("no event location detected");
} else {
edit_text_event_location.setText(event.location);
layout_event_location.setEndIconVisible(true);
}
if (event.description.equals("")) {
edit_text_event_description.setError("no event description detected");
} else {
edit_text_event_description.setText(event.description);
}
Picasso.get()
.load(event.image_url)
.placeholder(R.drawable.ic_banner_foreground)
.into(image_view_toolbar);
ok_button.setEnabled(true); ok_button.setEnabled(true);
} }

View File

@ -3,6 +3,7 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
@ -16,7 +17,7 @@
android:theme="@style/AppTheme.AppBarOverlay"> android:theme="@style/AppTheme.AppBarOverlay">
<com.google.android.material.appbar.CollapsingToolbarLayout <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout" android:id="@+id/layout_toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/colorPrimary" android:background="@color/colorPrimary"
@ -63,7 +64,7 @@
<include layout="@layout/content_main" /> <include layout="@layout/content_main" />
<com.google.android.material.bottomappbar.BottomAppBar <com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomButtonLayout" android:id="@+id/bottom_appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:layout_gravity="bottom"

View File

@ -1,59 +1,50 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nested_scroll_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:animateLayoutChanges="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".MainActivity" tools:context=".MainActivity"
tools:showIn="@layout/activity_main" tools:showIn="@layout/activity_main">
android:id="@+id/nested_scroll_view" >
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout" <LinearLayout
android:id="@+id/linear_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="32dp"
android:layout_marginBottom="64dp"
<LinearLayout android:divider="@drawable/divider"
android:id="@+id/linearLayout4" android:orientation="vertical"
android:layout_width="0dp" android:showDividers="middle">
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout" android:id="@+id/layout_uri_input"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:endIconCheckable="false" app:endIconCheckable="false"
app:endIconDrawable="@drawable/ic_backspace_black" app:endIconDrawable="@drawable/ic_backspace_black"
app:endIconMode="custom"
app:errorIconDrawable="@drawable/ic_backspace_black" app:errorIconDrawable="@drawable/ic_backspace_black"
app:endIconMode="custom"
app:helperText="@string/add_link_helper" app:helperText="@string/add_link_helper"
app:helperTextEnabled="true" app:helperTextEnabled="true">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout4">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_uri_input" android:id="@+id/edit_text_uri_input"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autoLink="web" android:autoLink="web"
android:clickable="true"
android:cursorVisible="true" android:cursorVisible="true"
android:hint="@string/add_link_hint" android:hint="@string/add_link_hint"
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions"
@ -61,127 +52,94 @@
android:textColorLink="@color/material_on_background_emphasis_high_type" /> android:textColorLink="@color/material_on_background_emphasis_high_type" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<LinearLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/scrollView" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/spacer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout">
<LinearLayout <com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_text_event_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:divider="@drawable/divider" android:focusable="false"
android:orientation="vertical" android:cursorVisible="false"
android:showDividers="middle"> android:hint="Event name"
android:inputType="textNoSuggestions"
android:singleLine="true"
android:textColorLink="@color/material_on_background_emphasis_high_type" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_event_name" android:id="@+id/edit_text_event_start"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clickable="true" android:focusable="false"
android:cursorVisible="true" android:cursorVisible="false"
android:hint="Event name" android:hint="Event start"
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions"
android:singleLine="true" android:singleLine="true" />
android:textColorLink="@color/material_on_background_emphasis_high_type" /> </com.google.android.material.textfield.TextInputLayout>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_event_start" android:id="@+id/edit_text_event_end"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clickable="true" android:focusable="false"
android:cursorVisible="true" android:cursorVisible="false"
android:inputType="textNoSuggestions" android:hint="Event end"
android:hint="Event start" android:inputType="textNoSuggestions"
android:singleLine="true" /> android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_event_end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:cursorVisible="true"
android:inputType="textNoSuggestions"
android:hint="Event end"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:id="@+id/layout_event_location"
android:id="@+id/location_layout" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:endIconDrawable="@drawable/ic_add_location" app:endIconDrawable="@drawable/ic_add_location"
app:endIconMode="custom"> app:endIconMode="custom">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_event_location" android:id="@+id/edit_text_event_location"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autoLink="map" android:autoLink="map"
android:clickable="true" android:focusable="false"
android:cursorVisible="true" android:cursorVisible="false"
android:hint="Event location" android:hint="Event location"
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions"
android:singleLine="true" /> android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/field_event_description" android:id="@+id/edit_text_event_description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autoLink="web" android:autoLink="web"
android:clickable="true" android:focusable="false"
android:cursorVisible="true" android:cursorVisible="false"
android:hint="Event description" android:hint="Event description"
android:inputType="textNoSuggestions|textMultiLine" android:inputType="textNoSuggestions|textMultiLine"
android:singleLine="false" android:singleLine="false"
android:textColorLink="@color/material_on_background_emphasis_high_type" /> android:textColorLink="@color/material_on_background_emphasis_high_type" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </LinearLayout>
</LinearLayout>
<Space
android:id="@+id/spacer"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="@id/constraint_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>