Merge remote-tracking branch 'refs/remotes/origin/master' into directNearbyUploads

This commit is contained in:
misaochan 2017-12-31 17:22:55 +10:00
commit e979d88aef
42 changed files with 879 additions and 288 deletions

View file

@ -8,10 +8,18 @@ import butterknife.ButterKnife;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.ui.widget.HtmlTextView;
/**
* Represents about screen of this app
*/
public class AboutActivity extends NavigationBaseActivity {
@BindView(R.id.about_version) TextView versionText;
@BindView(R.id.about_license) HtmlTextView aboutLicenseText;
/**
* This method helps in the creation About screen
*
* @param savedInstanceState Data bundle
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View file

@ -49,16 +49,21 @@ public class CommonsApplication extends DaggerApplication {
@Inject @Named("default_preferences") SharedPreferences defaultPrefs;
@Inject @Named("application_preferences") SharedPreferences applicationPrefs;
@Inject @Named("prefs") SharedPreferences otherPrefs;
public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using Android Commons app";
public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
public static final String LOGS_PRIVATE_EMAIL = "commons-app-android-private@googlegroups.com";
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
private CommonsApplicationComponent component;
private RefWatcher refWatcher;
/**
* Used to declare and initialize various components and dependencies
*/
@Override
public void onCreate() {
super.onCreate();
@ -80,23 +85,41 @@ public class CommonsApplication extends DaggerApplication {
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
}
/**
* Helps in setting up LeakCanary library
* @return instance of LeakCanary
*/
protected RefWatcher setupLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
}
return LeakCanary.install(this);
}
/**
* Provides a way to get member refWatcher
*
* @param context Application context
* @return application member refWatcher
*/
public static RefWatcher getRefWatcher(Context context) {
CommonsApplication application = (CommonsApplication) context.getApplicationContext();
return application.refWatcher;
}
/**
* Helps in injecting dependency library Dagger
* @return Dagger injector
*/
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return injector();
}
/**
* used to create injector of application component
* @return Application component of Dagger
*/
public CommonsApplicationComponent injector() {
if (component == null) {
component = DaggerCommonsApplicationComponent.builder()
@ -106,6 +129,11 @@ public class CommonsApplication extends DaggerApplication {
return component;
}
/**
* clears data of current application
* @param context Application context
* @param logoutListener Implementation of interface LogoutListener
*/
public void clearApplicationData(Context context, LogoutListener logoutListener) {
File cacheDirectory = context.getCacheDir();
File applicationDirectory = new File(cacheDirectory.getParent());
@ -145,6 +173,9 @@ public class CommonsApplication extends DaggerApplication {
Contribution.Table.onDelete(db);
}
/**
* Interface used to get log-out events
*/
public interface LogoutListener {
void onLogoutComplete();
}

View file

@ -2,6 +2,9 @@ package fr.free.nrw.commons;
import android.support.annotation.Nullable;
/**
* represents Licence object
*/
public class License {
private String key;
private String template;
@ -56,6 +59,12 @@ public class License {
}
}
/**
* Gets the license URL
*
* @param language license language
* @return URL
*/
public @Nullable String getUrl(String language) {
if (url == null) {
return null;

View file

@ -13,10 +13,18 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* Represents a list of Licenses
*/
public class LicenseList {
private Map<String, License> licenses = new HashMap<>();
private Resources res;
/**
* Constructs new instance of LicenceList
*
* @param activity License activity
*/
public LicenseList(Activity activity) {
res = activity.getResources();
XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses);
@ -31,14 +39,28 @@ public class LicenseList {
}
}
/**
* Gets a collection of licenses
* @return License values
*/
public Collection<License> values() {
return licenses.values();
}
/**
* Gets license
* @param key License key
* @return License that matches key
*/
public License get(String key) {
return licenses.get(key);
}
/**
* Creates a license from template
* @param template License template
* @return null
*/
@Nullable
License licenseForTemplate(String template) {
String ucTemplate = new PageTitle(template).getDisplayText();
@ -50,6 +72,11 @@ public class LicenseList {
return null;
}
/**
* Gets template name id
* @param template License template
* @return name id of template
*/
private String nameIdForTemplate(String template) {
// hack :D (converts dashes and periods to underscores)
// cc-by-sa-3.0 -> cc_by_sa_3_0
@ -57,6 +84,11 @@ public class LicenseList {
"_").replace(".", "_");
}
/**
* Gets name of given template
* @param template License template
* @return name of template
*/
private String nameForTemplate(String template) {
int nameId = res.getIdentifier("fr.free.nrw.commons:string/"
+ nameIdForTemplate(template), null, null);
@ -92,4 +124,4 @@ public class LicenseList {
}
}
}
}

View file

@ -47,16 +47,35 @@ public class Media implements Parcelable {
private HashMap<String, Object> tags = new HashMap<>();
private @Nullable LatLng coordinates;
/**
* Provides local constructor
*/
protected Media() {
this.categories = new ArrayList<>();
this.descriptions = new HashMap<>();
}
/**
* Provides a minimal constructor
*
* @param filename Media filename
*/
public Media(String filename) {
this();
this.filename = filename;
}
/**
* Provide Media constructor
* @param localUri Media URI
* @param imageUrl Media image URL
* @param filename Media filename
* @param description Media description
* @param dataLength Media date length
* @param dateCreated Media creation date
* @param dateUploaded Media date uploaded
* @param creator Media creator
*/
public Media(Uri localUri, String imageUrl, String filename, String description,
long dataLength, Date dateCreated, @Nullable Date dateUploaded, String creator) {
this();
@ -90,19 +109,33 @@ public class Media implements Parcelable {
descriptions = in.readHashMap(ClassLoader.getSystemClassLoader());
}
/**
* Gets tag of media
* @param key Media key
* @return Media tag
*/
public Object getTag(String key) {
return tags.get(key);
}
/**
* Modifies( or creates a) tag of media
* @param key Media key
* @param value Media value
*/
public void setTag(String key, Object value) {
tags.put(key, value);
}
/**
* Gets media display title
* @return Media title
*/
public String getDisplayTitle() {
if (filename == null) {
return "";
}
// FIXME: Gross hack bercause my regex skills suck maybe or I am too lazy who knows
// FIXME: Gross hack because my regex skills suck maybe or I am too lazy who knows
String title = getFilePageTitle().getDisplayText().replaceFirst("^File:", "");
Matcher matcher = displayTitlePattern.matcher(title);
if (matcher.matches()) {
@ -112,14 +145,27 @@ public class Media implements Parcelable {
}
}
/**
* Gets file page title
* @return New media page title
*/
public PageTitle getFilePageTitle() {
return new PageTitle("File:" + getFilename().replaceFirst("^File:", ""));
}
/**
* Gets local URI
* @return Media local URI
*/
public Uri getLocalUri() {
return localUri;
}
/**
* Gets image URL
* can be null.
* @return Image URL
*/
@Nullable
public String getImageUrl() {
if (imageUrl == null && this.getFilename() != null) {
@ -304,6 +350,10 @@ public class Media implements Parcelable {
this.categories.addAll(categories);
}
/**
* Modifies (or sets) media descriptions
* @param descriptions Media descriptions
*/
void setDescriptions(Map<String, String> descriptions) {
for (String key : this.descriptions.keySet()) {
this.descriptions.remove(key);
@ -313,6 +363,11 @@ public class Media implements Parcelable {
}
}
/**
* Gets media description in preferred language
* @param preferredLanguage Language preferred
* @return Description in preferred language
*/
public String getDescription(String preferredLanguage) {
if (descriptions.containsKey(preferredLanguage)) {
// See if the requested language is there.
@ -329,11 +384,21 @@ public class Media implements Parcelable {
}
}
/**
* Method of Parcelable interface
* @return zero
*/
@Override
public int describeContents() {
return 0;
}
/**
* Creates a way to transfer information between two or more
* activities.
* @param parcel Instance of Parcel
* @param flags Parcel flag
*/
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(localUri, flags);

View file

@ -37,12 +37,23 @@ public class Utils {
}
}
/**
* Creates an URL for thumbnail
*
* @param filename Thumbnail file name
* @return URL of thumbnail
*/
public static String makeThumbBaseUrl(@NonNull String filename) {
String name = new PageTitle(filename).getPrefixedText();
String sha = new String(Hex.encodeHex(DigestUtils.md5(name)));
return String.format("%s/%s/%s/%s", BuildConfig.IMAGE_URL_BASE, sha.substring(0, 1), sha.substring(0, 2), urlEncode(name));
}
/**
* URL Encode an URL in UTF-8 format
* @param url Unformatted URL
* @return Encoded URL
*/
public static String urlEncode(String url) {
try {
return URLEncoder.encode(url, "utf-8");
@ -61,6 +72,11 @@ public class Utils {
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
}
/**
* Generates licence name with given ID
* @param license License ID
* @return Name of license
*/
public static int licenseNameFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
@ -81,6 +97,12 @@ public class Utils {
throw new RuntimeException("Unrecognized license value: " + license);
}
/**
* Fixing incorrect extension
* @param title File name
* @param extension Correct extension
* @return File with correct extension
*/
public static String fixExtension(String title, String extension) {
Pattern jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE);
@ -96,6 +118,11 @@ public class Utils {
return title;
}
/**
* Tells whether dark theme is active or not
* @param context Activity context
* @return The state of dark theme
*/
public static boolean isDarkTheme(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme", false);
}

View file

@ -18,6 +18,11 @@ public class WelcomeActivity extends BaseActivity {
private WelcomePagerAdapter adapter = new WelcomePagerAdapter();
/**
* Initialises exiting fields and dependencies
*
* @param savedInstanceState WelcomeActivity bundled data
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -30,12 +35,20 @@ public class WelcomeActivity extends BaseActivity {
adapter.setCallback(this::finish);
}
/**
* References WelcomePageAdapter to null before the activity is destroyed
*/
@Override
public void onDestroy() {
adapter.setCallback(null);
super.onDestroy();
}
/**
* Creates a way to change current activity to WelcomeActivity
*
* @param context Activity context
*/
public static void startYourself(Context context) {
Intent welcomeIntent = new Intent(context, WelcomeActivity.class);
context.startActivity(welcomeIntent);

View file

@ -10,13 +10,6 @@ import butterknife.ButterKnife;
import butterknife.OnClick;
public class WelcomePagerAdapter extends PagerAdapter {
private static final int PAGE_FINAL = 4;
private Callback callback;
public interface Callback {
void onYesClicked();
}
static final int[] PAGE_LAYOUTS = new int[]{
R.layout.welcome_wikipedia,
R.layout.welcome_do_upload,
@ -24,16 +17,34 @@ public class WelcomePagerAdapter extends PagerAdapter {
R.layout.welcome_image_details,
R.layout.welcome_final
};
private static final int PAGE_FINAL = 4;
private Callback callback;
/**
* Changes callback to provided one
*
* @param callback New callback
* it can be null.
*/
public void setCallback(@Nullable Callback callback) {
this.callback = callback;
}
/**
* Gets total number of layouts
* @return Number of layouts
*/
@Override
public int getCount() {
return PAGE_LAYOUTS.length;
}
/**
* Compares given view with provided object
* @param view Adapter view
* @param object Adapter object
* @return Equality between view and object
*/
@Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
@ -52,16 +63,29 @@ public class WelcomePagerAdapter extends PagerAdapter {
return layout;
}
/**
* Provides a way to remove an item from container
* @param container Adapter view group container
* @param position Index of item
* @param obj Adapter object
*/
@Override
public void destroyItem(ViewGroup container, int position, Object obj) {
container.removeView((View) obj);
}
public interface Callback {
void onYesClicked();
}
class ViewHolder {
ViewHolder(View view) {
ButterKnife.bind(this, view);
}
/**
* Triggers on click callback on button click
*/
@OnClick(R.id.welcomeYesButton)
void onClicked() {
if (callback != null) {

View file

@ -14,6 +14,9 @@ import java.util.Date;
import fr.free.nrw.commons.category.CategoryContentProvider;
/**
* Represents a category
*/
public class Category {
private Uri contentUri;
@ -22,36 +25,72 @@ public class Category {
private int timesUsed;
// Getters/setters
/**
* Gets name
*
* @return name
*/
public String getName() {
return name;
}
/**
* Modifies name
*
* @param name Category name
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets last used date
*
* @return Last used date
*/
private Date getLastUsed() {
// warning: Date objects are mutable.
return (Date)lastUsed.clone();
}
/**
* Modifies last used date
*
* @param lastUsed Category date
*/
public void setLastUsed(Date lastUsed) {
// warning: Date objects are mutable.
this.lastUsed = (Date)lastUsed.clone();
}
/**
* Generates new last used date
*/
private void touch() {
lastUsed = new Date();
}
/**
* Gets no. of times the category is used
*
* @return no. of times used
*/
private int getTimesUsed() {
return timesUsed;
}
/**
* Modifies no. of times used
*
* @param timesUsed Category used times
*/
public void setTimesUsed(int timesUsed) {
this.timesUsed = timesUsed;
}
/**
* Increments timesUsed by 1 and sets last used date as now.
*/
public void incTimesUsed() {
timesUsed++;
touch();
@ -75,6 +114,11 @@ public class Category {
}
}
/**
* Gets content values
*
* @return Content values
*/
private ContentValues toContentValues() {
ContentValues cv = new ContentValues();
cv.put(Table.COLUMN_NAME, getName());
@ -83,6 +127,11 @@ public class Category {
return cv;
}
/**
* Gets category from cursor
* @param cursor Category cursor
* @return Category from cursor
*/
private static Category fromCursor(Cursor cursor) {
// Hardcoding column positions!
Category c = new Category();
@ -175,15 +224,30 @@ public class Category {
+ COLUMN_TIMES_USED + " INTEGER"
+ ");";
/**
* Creates new table with provided SQLite database
*
* @param db Category database
*/
public static void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_STATEMENT);
}
/**
* Deletes existing table
* @param db Category database
*/
public static void onDelete(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
/**
* Updates given database
* @param db Category database
* @param from Exiting category id
* @param to New category id
*/
public static void onUpdate(SQLiteDatabase db, int from, int to) {
if (from == to) {
return;
@ -209,4 +273,4 @@ public class Category {
}
}
//endregion
}
}

View file

@ -134,4 +134,4 @@ public class LatLng {
public Uri getGmmIntentUri() {
return Uri.parse("geo:0,0?q=" + latitude + "," + longitude);
}
}
}

View file

@ -384,7 +384,7 @@ public class MediaDetailFragment extends DaggerFragment {
private void openMap(LatLng coordinates) {
//Open map app at given position
Uri gmmIntentUri = Uri.parse(
"geo:0,0?q=" + coordinates.getLatitude() + "," + coordinates.getLatitude());
"geo:0,0?q=" + coordinates.getLatitude() + "," + coordinates.getLongitude());
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {

View file

@ -22,6 +22,14 @@ public class LogBuilder {
private final String schema;
private final SharedPreferences prefs;
/**
* Main constructor of LogBuilder
*
* @param schema Log schema
* @param revision Log revision
* @param mwApi Wiki media API instance
* @param prefs Instance of SharedPreferences
*/
LogBuilder(String schema, long revision, MediaWikiApi mwApi, SharedPreferences prefs) {
this.prefs = prefs;
this.data = new JSONObject();
@ -30,6 +38,12 @@ public class LogBuilder {
this.mwApi = mwApi;
}
/**
* Adds data to preferences
* @param key Log key
* @param value Log object value
* @return LogBuilder
*/
public LogBuilder param(String key, Object value) {
try {
data.put(key, value);
@ -39,6 +53,10 @@ public class LogBuilder {
return this;
}
/**
* Encodes JSON object to URL
* @return URL to JSON object
*/
URL toUrl() {
JSONObject fullData = new JSONObject();
try {
@ -65,7 +83,7 @@ public class LogBuilder {
LogTask logTask = new LogTask(mwApi);
logTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this);
}
public void log() {
log(false);
}

View file

@ -6,10 +6,20 @@ class LogTask extends AsyncTask<LogBuilder, Void, Boolean> {
private final MediaWikiApi mwApi;
/**
* Main constructor of LogTask
*
* @param mwApi Media wiki API instance
*/
public LogTask(MediaWikiApi mwApi) {
this.mwApi = mwApi;
}
/**
* Logs events in background
* @param logBuilders LogBuilder instance
* @return Background success state ( TRUE or FALSE )
*/
@Override
protected Boolean doInBackground(LogBuilder... logBuilders) {
return mwApi.logEvents(logBuilders);

View file

@ -4,15 +4,29 @@ public class MediaResult {
private final String wikiSource;
private final String parseTreeXmlSource;
/**
* Full-fledged constructor of MediaResult
*
* @param wikiSource Media wiki source
* @param parseTreeXmlSource Media tree parsed in XML
*/
MediaResult(String wikiSource, String parseTreeXmlSource) {
this.wikiSource = wikiSource;
this.parseTreeXmlSource = parseTreeXmlSource;
}
/**
* Gets wiki source
* @return Wiki source
*/
public String getWikiSource() {
return wikiSource;
}
/**
* Gets tree parsed in XML
* @return XML parsed tree
*/
public String getParseTreeXmlSource() {
return parseTreeXmlSource;
}

View file

@ -9,11 +9,24 @@ public class UploadResult {
private String imageUrl;
private String canonicalFilename;
/**
* Minimal constructor
*
* @param resultStatus Upload result status
* @param errorCode Upload error code
*/
UploadResult(String resultStatus, String errorCode) {
this.resultStatus = resultStatus;
this.errorCode = errorCode;
}
/**
* Full-fledged constructor
* @param resultStatus Upload result status
* @param dateUploaded Uploaded date
* @param canonicalFilename Uploaded file name
* @param imageUrl Uploaded image file name
*/
UploadResult(String resultStatus, Date dateUploaded, String canonicalFilename, String imageUrl) {
this.resultStatus = resultStatus;
this.dateUploaded = dateUploaded;
@ -21,22 +34,42 @@ public class UploadResult {
this.imageUrl = imageUrl;
}
/**
* Gets uploaded date
* @return Upload date
*/
public Date getDateUploaded() {
return dateUploaded;
}
/**
* Gets image url
* @return Uploaded image url
*/
public String getImageUrl() {
return imageUrl;
}
/**
* Gets canonical file name
* @return Uploaded file name
*/
public String getCanonicalFilename() {
return canonicalFilename;
}
/**
* Gets upload error code
* @return Error code
*/
public String getErrorCode() {
return errorCode;
}
/**
* Gets upload result status
* @return Upload result status
*/
public String getResultStatus() {
return resultStatus;
}

View file

@ -1,55 +1,70 @@
package fr.free.nrw.commons.settings;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatDelegate;
import android.view.MenuItem;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
public class SettingsActivity extends NavigationBaseActivity {
private AppCompatDelegate settingsDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
// Check prefs on every activity starts
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme",false)) {
setTheme(R.style.DarkAppTheme);
} else {
setTheme(R.style.LightAppTheme);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
ButterKnife.bind(this);
initDrawer();
}
// Get an action bar
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
if (settingsDelegate == null) {
settingsDelegate = AppCompatDelegate.create(this, null);
}
settingsDelegate.onPostCreate(savedInstanceState);
//Get an up button
//settingsDelegate.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
//Handle action-bar clicks
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
package fr.free.nrw.commons.settings;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatDelegate;
import android.view.MenuItem;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
/**
* allows the user to change the settings
*/
public class SettingsActivity extends NavigationBaseActivity {
private AppCompatDelegate settingsDelegate;
/**
* to be called when the activity starts
* @param savedInstanceState the previously saved state
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
// Check prefs on every activity starts
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme",false)) {
setTheme(R.style.DarkAppTheme);
} else {
setTheme(R.style.LightAppTheme);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
ButterKnife.bind(this);
initDrawer();
}
// Get an action bar
/**
* takes care of actions taken after the creation has happened
* @param savedInstanceState the saved state
*/
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
if (settingsDelegate == null) {
settingsDelegate = AppCompatDelegate.create(this, null);
}
settingsDelegate.onPostCreate(savedInstanceState);
//Get an up button
//settingsDelegate.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
/**
* Handle action-bar clicks
* @param item the selected item
* @return true on success, false on failure
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View file

@ -1,73 +1,96 @@
package fr.free.nrw.commons.ui.widget;
/**
* Created by mikel on 07/08/2017.
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.AppCompatDrawableManager;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.utils.UiUtils;
public class CompatTextView extends AppCompatTextView {
public CompatTextView(Context context) {
super(context);
init(null);
}
public CompatTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(@Nullable AttributeSet attrs) {
if (attrs != null) {
Context context = getContext();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView);
// Obtain DrawableManager used to pull Drawables safely, and check if we're in RTL
AppCompatDrawableManager dm = AppCompatDrawableManager.get();
boolean rtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
// Grab the compat drawable padding from the XML
float drawablePadding = a.getDimension(R.styleable.CompatTextView_drawablePadding, 0);
// Grab the compat drawable resources from the XML
int startDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableStart, 0);
int topDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableTop, 0);
int endDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableEnd, 0);
int bottomDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableBottom, 0);
// Load the used drawables, fall back to whatever was set in an "android:"
Drawable[] currentDrawables = getCompoundDrawables();
Drawable left = startDrawableRes != 0
? dm.getDrawable(context, startDrawableRes) : currentDrawables[0];
Drawable right = endDrawableRes != 0
? dm.getDrawable(context, endDrawableRes) : currentDrawables[1];
Drawable top = topDrawableRes != 0
? dm.getDrawable(context, topDrawableRes) : currentDrawables[2];
Drawable bottom = bottomDrawableRes != 0
? dm.getDrawable(context, bottomDrawableRes) : currentDrawables[3];
// Account for RTL and apply the compound Drawables
Drawable start = rtl ? right : left;
Drawable end = rtl ? left : right;
setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
setCompoundDrawablePadding((int) UiUtils.convertDpToPixel(drawablePadding, getContext()));
a.recycle();
}
}
}
package fr.free.nrw.commons.ui.widget;
/**
* Created by mikel on 07/08/2017.
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.AppCompatDrawableManager;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.utils.UiUtils;
/**
* a text view compatible with older versions of the platform
*/
public class CompatTextView extends AppCompatTextView {
/**
* Constructs a new instance of CompatTextView
* @param context the view context
*/
public CompatTextView(Context context) {
super(context);
init(null);
}
/**
* Constructs a new instance of CompatTextView
* @param context the view context
* @param attrs the set of attributes for the view
*/
public CompatTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
/**
* Constructs a new instance of CompatTextView
* @param context
* @param attrs
* @param defStyleAttr
*/
public CompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
/**
* initializes the view
* @param attrs the attribute set of the view, which can be null
*/
private void init(@Nullable AttributeSet attrs) {
if (attrs != null) {
Context context = getContext();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView);
// Obtain DrawableManager used to pull Drawables safely, and check if we're in RTL
AppCompatDrawableManager dm = AppCompatDrawableManager.get();
boolean rtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
// Grab the compat drawable padding from the XML
float drawablePadding = a.getDimension(R.styleable.CompatTextView_drawablePadding, 0);
// Grab the compat drawable resources from the XML
int startDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableStart, 0);
int topDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableTop, 0);
int endDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableEnd, 0);
int bottomDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableBottom, 0);
// Load the used drawables, fall back to whatever was set in an "android:"
Drawable[] currentDrawables = getCompoundDrawables();
Drawable left = startDrawableRes != 0
? dm.getDrawable(context, startDrawableRes) : currentDrawables[0];
Drawable right = endDrawableRes != 0
? dm.getDrawable(context, endDrawableRes) : currentDrawables[1];
Drawable top = topDrawableRes != 0
? dm.getDrawable(context, topDrawableRes) : currentDrawables[2];
Drawable bottom = bottomDrawableRes != 0
? dm.getDrawable(context, bottomDrawableRes) : currentDrawables[3];
// Account for RTL and apply the compound Drawables
Drawable start = rtl ? right : left;
Drawable end = rtl ? left : right;
setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
setCompoundDrawablePadding((int) UiUtils.convertDpToPixel(drawablePadding, getContext()));
a.recycle();
}
}
}

View file

@ -1,42 +1,51 @@
package fr.free.nrw.commons.ui.widget;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.AppCompatTextView;
import android.text.Html;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
/**
* An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any
* links clickable.
*/
public class HtmlTextView extends AppCompatTextView {
public HtmlTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setMovementMethod(LinkMovementMethod.getInstance());
setText(fromHtml(getText().toString()));
}
public void setHtmlText(String newText) {
setText(fromHtml(newText));
}
/**
* Fix Html.fromHtml is deprecated problem
*
* @param source provided Html string
* @return returned Spanned of appropriate method according to version check
*/
private static Spanned fromHtml(String source) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
} else {
//noinspection deprecation
return Html.fromHtml(source);
}
}
}
package fr.free.nrw.commons.ui.widget;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.AppCompatTextView;
import android.text.Html;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
/**
* An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any
* links clickable.
*/
public class HtmlTextView extends AppCompatTextView {
/**
* Constructs a new instance of HtmlTextView
* @param context the context of the view
* @param attrs the set of attributes for the view
*/
public HtmlTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setMovementMethod(LinkMovementMethod.getInstance());
setText(fromHtml(getText().toString()));
}
/**
* Sets the text to be displayed
* @param newText the text to be displayed
*/
public void setHtmlText(String newText) {
setText(fromHtml(newText));
}
/**
* Fix Html.fromHtml is deprecated problem
*
* @param source provided Html string
* @return returned Spanned of appropriate method according to version check
*/
private static Spanned fromHtml(String source) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
} else {
//noinspection deprecation
return Html.fromHtml(source);
}
}
}

View file

@ -1,46 +1,69 @@
package fr.free.nrw.commons.ui.widget;
import android.app.Dialog;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
public abstract class OverlayDialog extends DialogFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_FRAME, android.R.style.Theme_Holo_Light);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
setDialogLayoutToFullScreen();
super.onViewCreated(view, savedInstanceState);
}
private void setDialogLayoutToFullScreen() {
Window window = getDialog().getWindow();
WindowManager.LayoutParams wlp = window.getAttributes();
window.requestFeature(Window.FEATURE_NO_TITLE);
wlp.gravity = Gravity.BOTTOM;
wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
wlp.height = WindowManager.LayoutParams.MATCH_PARENT;
window.setAttributes(wlp);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
Window window = dialog.getWindow();
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
return dialog;
}
}
package fr.free.nrw.commons.ui.widget;
import android.app.Dialog;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
/**
* a formatted dialog fragment
* This class is used by NearbyInfoDialog
*/
public abstract class OverlayDialog extends DialogFragment {
/**
* creates a DialogFragment with the correct style and theme
* @param savedInstanceState
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_FRAME, android.R.style.Theme_Holo_Light);
}
/**
* When the view is created, sets the dialog layout to full screen
*
* @param view the view being used
* @param savedInstanceState bundle re-constructed from a previous saved state
*/
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
setDialogLayoutToFullScreen();
super.onViewCreated(view, savedInstanceState);
}
/**
* sets the dialog layout to fullscreen
*/
private void setDialogLayoutToFullScreen() {
Window window = getDialog().getWindow();
WindowManager.LayoutParams wlp = window.getAttributes();
window.requestFeature(Window.FEATURE_NO_TITLE);
wlp.gravity = Gravity.BOTTOM;
wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
wlp.height = WindowManager.LayoutParams.MATCH_PARENT;
window.setAttributes(wlp);
}
/**
* builds custom dialog container
*
* @param savedInstanceState the previously saved state
* @return the dialog
*/
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
Window window = dialog.getWindow();
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
return dialog;
}
}

View file

@ -37,6 +37,13 @@ public class LengthUtils {
return computeAngleBetween(from, to) * 6371009.0D; // Earth's radius in meter
}
/**
* Computes angle between two points
*
* @param from Point A
* @param to Point B
* @return Angle in radius
*/
private static double computeAngleBetween(LatLng from, LatLng to) {
return distanceRadians(Math.toRadians(from.getLatitude()),
Math.toRadians(from.getLongitude()),
@ -44,18 +51,43 @@ public class LengthUtils {
Math.toRadians(to.getLongitude()));
}
/**
* Computes arc length between 2 points
* @param lat1 Latitude of point A
* @param lng1 Longitude of point A
* @param lat2 Latitude of point B
* @param lng2 Longitude of point B
* @return Arc length between the points
*/
private static double distanceRadians(double lat1, double lng1, double lat2, double lng2) {
return arcHav(havDistance(lat1, lat2, lng1 - lng2));
}
/**
* Computes inverse of haversine
* @param x Angle in radian
* @return Inverse of haversine
*/
private static double arcHav(double x) {
return 2.0D * Math.asin(Math.sqrt(x));
}
/**
* Computes distance between two points that are on same Longitude
* @param lat1 Latitude of point A
* @param lat2 Latitude of point B
* @param longitude Longitude on which they lie
* @return Arc length between points
*/
private static double havDistance(double lat1, double lat2, double longitude) {
return hav(lat1 - lat2) + hav(longitude) * Math.cos(lat1) * Math.cos(lat2);
}
/**
* Computes haversine
* @param x Angle in radians
* @return Haversine of x
*/
private static double hav(double x) {
double sinHalf = Math.sin(x * 0.5D);
return sinHalf * sinHalf;