Convert more code to Kotlin

This commit is contained in:
ligi 2016-10-24 16:41:50 +02:00
parent 296d9211b2
commit 13586cc08a
No known key found for this signature in database
GPG key ID: 8E81894010ABF23D
8 changed files with 458 additions and 513 deletions

View file

@ -138,6 +138,8 @@ dependencies {
exclude group: 'javax.inject'
}
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
androidTestCompile 'com.squareup.spoon:spoon-client:1.7.0'
androidTestCompile 'com.squareup.assertj:assertj-android:1.1.1'

View file

@ -1,44 +1,52 @@
package org.ligi.passandroid;
import android.annotation.TargetApi;
import android.support.test.filters.MediumTest;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.support.test.espresso.intent.rule.IntentsTestRule;
import com.squareup.spoon.Spoon;
import javax.inject.Inject;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.PassType;
import org.ligi.passandroid.ui.PassEditActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.clearText;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.scrollTo;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.Intents.intending;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.assertj.core.api.Assertions.assertThat;
@TargetApi(14)
public class ThePassEditActivity extends BaseIntegration<PassEditActivity> {
public class ThePassEditActivity {
@Rule
public IntentsTestRule<PassEditActivity> rule = new IntentsTestRule<>(PassEditActivity.class, false, false);
@Inject
PassStore passStore;
public ThePassEditActivity() {
super(PassEditActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
@Before
public void setUp() {
final TestComponent build = DaggerTestComponent.create();
build.inject(this);
App.setComponent(build);
getActivity();
rule.launchActivity(null);
}
@MediumTest
@Test
public void testSetToEventWorks() {
onView(withId(R.id.categoryView)).perform(click());
@ -47,10 +55,10 @@ public class ThePassEditActivity extends BaseIntegration<PassEditActivity> {
onView(withText(R.string.category_event)).perform(click());
assertThat(passStore.getCurrentPass().getType()).isEqualTo(PassType.EVENT);
Spoon.screenshot(getActivity(), "edit_set_event");
Spoon.screenshot(rule.getActivity(), "edit_set_event");
}
@MediumTest
@Test
public void testSetToCouponWorks() {
onView(withId(R.id.categoryView)).perform(click());
@ -58,20 +66,20 @@ public class ThePassEditActivity extends BaseIntegration<PassEditActivity> {
onView(withText(R.string.category_coupon)).perform(click());
assertThat(passStore.getCurrentPass().getType()).isEqualTo(PassType.COUPON);
Spoon.screenshot(getActivity(), "edit_set_coupon");
Spoon.screenshot(rule.getActivity(), "edit_set_coupon");
}
@MediumTest
@Test
public void testSetDescriptionWorks() {
onView(withId(R.id.passTitle)).perform(clearText(), typeText("test description"));
assertThat(passStore.getCurrentPass().getDescription()).isEqualTo("test description");
Spoon.screenshot(getActivity(), "edit_set_description");
Spoon.screenshot(rule.getActivity(), "edit_set_description");
}
@MediumTest
@Test
public void testColorWheelIsThere() {
onView(withId(R.id.categoryView)).perform(click());
@ -79,7 +87,23 @@ public class ThePassEditActivity extends BaseIntegration<PassEditActivity> {
onView(withId(R.id.colorPicker)).check(matches(isDisplayed()));
Spoon.screenshot(getActivity(), "edit_set_color");
Spoon.screenshot(rule.getActivity(), "edit_set_color");
}
@Test
public void testAddAbortImagePick() {
int[] add_image_views = {R.id.add_footer, R.id.add_logo, R.id.add_strip};
for (final int res : add_image_views) {
intending(hasAction(Intent.ACTION_CHOOSER)).respondWith(new Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null));
onView(withId(res)).perform(scrollTo(), click());
intended(hasAction(Intent.ACTION_CHOOSER));
}
}
}

View file

@ -1,58 +0,0 @@
package org.ligi.passandroid.helper;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.ligi.passandroid.R;
import org.ligi.passandroid.model.PassBitmapDefinitions;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.Pass;
import org.ligi.passandroid.model.pass.PassField;
import org.ligi.passandroid.model.pass.PassImpl;
import org.ligi.passandroid.model.pass.PassType;
public class PassTemplates {
public static final String APP = "passandroid";
public static Pass createAndAddEmptyPass(final PassStore passStore, final Resources resources) {
final PassImpl pass = new PassImpl(UUID.randomUUID().toString());
pass.setAccentColor(0xFF0000ff);
pass.setDescription("custom Pass");
pass.setApp(APP);
pass.setType(PassType.EVENT);
passStore.setCurrentPass(pass);
passStore.save(pass);
final Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_launcher);
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 90, new FileOutputStream(new File(passStore.getPathForID(pass.getId()), PassBitmapDefinitions.BITMAP_ICON+".png")));
} catch (FileNotFoundException ignored) {
}
return pass;
}
public static Pass createPassForImageImport() {
final PassImpl pass = new PassImpl(UUID.randomUUID().toString());
pass.setAccentColor(0xFF0000ff);
pass.setDescription("image import");
List<PassField> list = new ArrayList<>();
list.add(new PassField(null, "source", "image", false));
list.add(new PassField(null, "note", "This is imported from image - not a real pass", false));
pass.setFields(list);
pass.setApp(APP);
pass.setType(PassType.EVENT);
return pass;
}
}

View file

@ -0,0 +1,58 @@
package org.ligi.passandroid.helper
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import org.ligi.passandroid.R
import org.ligi.passandroid.model.PassBitmapDefinitions
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.model.pass.PassField
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.model.pass.PassType
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.util.*
object PassTemplates {
val APP = "passandroid"
fun createAndAddEmptyPass(passStore: PassStore, resources: Resources): Pass {
val pass = createBasePass()
pass.description = "custom Pass"
passStore.currentPass = pass
passStore.save(pass)
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_launcher)
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 90, FileOutputStream(File(passStore.getPathForID(pass.id), PassBitmapDefinitions.BITMAP_ICON + ".png")))
} catch (ignored: FileNotFoundException) {
}
return pass
}
fun createPassForImageImport(): Pass {
return createBasePass().apply {
description = "image import"
fields = mutableListOf(
PassField(null, "source", "image", false),
PassField(null, "note", "This is imported from image - not a real pass", false)
)
}
}
private fun createBasePass(): PassImpl {
val pass = PassImpl(UUID.randomUUID().toString())
pass.accentColor = 0xFF0000FF.toInt()
pass.app = APP
pass.type = PassType.EVENT
return pass
}
}

View file

@ -41,7 +41,7 @@ class PassEditActivity : AppCompatActivity() {
@Inject
lateinit internal var bus: EventBus
private var passViewHelper: PassViewHelper? = null
private val passViewHelper: PassViewHelper by lazy { PassViewHelper(this) }
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
fun pickImage(req_code_pick_icon: Int) {
@ -85,8 +85,6 @@ class PassEditActivity : AppCompatActivity() {
fragmentTransaction.commit()
passViewHelper = PassViewHelper(this)
add_barcode_button.setOnClickListener {
BarcodePickDialog.show(this@PassEditActivity,
bus,
@ -101,7 +99,7 @@ class PassEditActivity : AppCompatActivity() {
refresh(currentPass)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
imageEditHelper.onActivityResult(requestCode, resultCode, data)
}
@ -121,7 +119,7 @@ class PassEditActivity : AppCompatActivity() {
prepareImageUI(R.id.footer_img, R.id.add_footer, ImageEditHelper.REQ_CODE_PICK_FOOTER)
add_barcode_button.visibility = if (pass.barCode == null) View.VISIBLE else View.GONE
val barcodeUIController = BarcodeUIController(window.decorView, pass.barCode, this, passViewHelper!!)
val barcodeUIController = BarcodeUIController(window.decorView, pass.barCode, this, passViewHelper)
barcodeUIController.barcode_img.setOnClickListener { BarcodePickDialog.show(this@PassEditActivity, bus, currentPass, currentPass.barCode) }
}
@ -139,7 +137,7 @@ class PassEditActivity : AppCompatActivity() {
}
val logoImage = findViewById(logo_img) as ImageView
passViewHelper!!.setBitmapSafe(logoImage, bitmap)
passViewHelper.setBitmapSafe(logoImage, bitmap)
logoImage.setOnClickListener(listener)
addButton.setOnClickListener(listener)
}

View file

@ -1,431 +0,0 @@
package org.ligi.passandroid.ui;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import java.util.Collection;
import net.i2p.android.ext.floatingactionbutton.FloatingActionButton;
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.ligi.axt.AXT;
import org.ligi.axt.listeners.ActivityFinishingOnClickListener;
import org.ligi.passandroid.App;
import org.ligi.passandroid.R;
import org.ligi.passandroid.events.PassStoreChangeEvent;
import org.ligi.passandroid.events.ScanFinishedEvent;
import org.ligi.passandroid.events.ScanProgressEvent;
import org.ligi.passandroid.helper.PassTemplates;
import org.ligi.passandroid.model.PassClassifier;
import org.ligi.passandroid.model.PassStoreProjection;
import org.ligi.passandroid.model.pass.Pass;
import org.ligi.snackengage.SnackContext;
import org.ligi.snackengage.SnackEngage;
import org.ligi.snackengage.conditions.AfterNumberOfOpportunities;
import org.ligi.snackengage.conditions.connectivity.IsConnectedViaWiFiOrUnknown;
import org.ligi.snackengage.snacks.BaseSnack;
import org.ligi.snackengage.snacks.DefaultRateSnack;
import org.ligi.snackengage.snacks.OpenURLSnack;
import org.ligi.tracedroid.TraceDroid;
import org.ligi.tracedroid.sending.TraceDroidEmailSender;
import permissions.dispatcher.NeedsPermission;
import permissions.dispatcher.OnNeverAskAgain;
import permissions.dispatcher.OnPermissionDenied;
import permissions.dispatcher.RuntimePermissions;
@RuntimePermissions
public class PassListActivity extends PassAndroidActivity {
private static final int OPEN_FILE_READ_REQUEST_CODE = 1000;
private ActionBarDrawerToggle drawerToggle;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.tab_layout)
TabLayout tabLayout;
@BindView(R.id.view_pager)
ViewPager viewPager;
@BindView(R.id.drawer_layout)
DrawerLayout drawer;
@BindView(R.id.emptyView)
TextView emptyView;
@BindView(R.id.fam)
FloatingActionsMenu floatingActionsMenu;
private PassTopicFragmentPagerAdapter adapter;
private ProgressDialog pd;
@OnClick(R.id.fab_action_create_pass)
void onFABClick() {
final Pass pass = PassTemplates.createAndAddEmptyPass(passStore, getResources());
floatingActionsMenu.collapse();
AXT.at(this).startCommonIntent().activityFromClass(PassEditActivity.class);
final String newTitle;
if (tabLayout.getSelectedTabPosition() < 0) {
newTitle = getString(R.string.topic_new);
} else {
newTitle = adapter.getPageTitle(tabLayout.getSelectedTabPosition()).toString();
}
passStore.getClassifier().moveToTopic(pass, newTitle);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onScanProgress(final ScanProgressEvent event) {
if (pd != null && pd.isShowing()) {
pd.setMessage(event.message);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onScanFinished(final ScanFinishedEvent event) {
if (pd != null && pd.isShowing()) {
final String message = getString(R.string.scan_finished_dialog_text, event.getFoundPasses().size());
pd.dismiss();
new AlertDialog.Builder(PassListActivity.this).setTitle(R.string.scan_finished_dialog_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show();
}
}
@TargetApi(16)
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
@OnNeverAskAgain(Manifest.permission.READ_EXTERNAL_STORAGE)
void showDeniedFor() {
Snackbar.make(floatingActionsMenu, "no permission to scan", Snackbar.LENGTH_INDEFINITE).show();
}
@OnClick(R.id.fab_action_scan)
void onScanClick() {
PassListActivityPermissionsDispatcher.scanWithCheck(this);
floatingActionsMenu.collapse();
}
@TargetApi(16)
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
void scan() {
final Intent intent = new Intent(this, SearchPassesIntentService.class);
startService(intent);
pd = new ProgressDialog(this);
pd.setTitle(R.string.scan_progressdialog_title);
pd.setMessage(getString(R.string.scan_progressdialog_message));
pd.setCancelable(false);
pd.setIndeterminate(true);
pd.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.scan_dialog_send_background_button), new ActivityFinishingOnClickListener(this));
pd.show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
PassListActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
@OnClick(R.id.fab_action_demo_pass)
void onAddDemoClick() {
AXT.at(this).startCommonIntent().openUrl("http://espass.it/examples");
floatingActionsMenu.collapse();
}
@BindView(R.id.fab_action_open_file)
FloatingActionButton openFileFAB;
public final static int VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK = 19;
@OnClick(R.id.fab_action_open_file)
@TargetApi(VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK)
void onAddOpenFileClick() {
try {
final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*"); // tried with octet stream - no use
startActivityForResult(intent, OPEN_FILE_READ_REQUEST_CODE);
} catch (ActivityNotFoundException e) {
Snackbar.make(floatingActionsMenu, "Unavailable", Snackbar.LENGTH_LONG).show();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == OPEN_FILE_READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
final Intent targetIntent = new Intent(this, PassImportActivity.class);
targetIntent.setData(resultData.getData());
startActivity(targetIntent);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
App.component().inject(this);
setContentView(R.layout.pass_list);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
AXT.at(openFileFAB).setVisibility(Build.VERSION.SDK_INT >= VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK);
// don't want too many windows in worst case - so check for errors first
if (TraceDroid.getStackTraceFiles().length > 0) {
tracker.trackEvent("ui_event", "send", "stacktraces", null);
if (settings.doTraceDroidEmailSend()) {
TraceDroidEmailSender.sendStackTraces("ligi@ligi.de", this);
}
} else { // if no error - check if there is a new version of the app
tracker.trackEvent("ui_event", "processFile", "updatenotice", null);
SnackEngage.from(floatingActionsMenu)
.withSnack(new DefaultRateSnack().withDuration(BaseSnack.DURATION_INDEFINITE))
.withSnack(new OpenURLSnack("market://details?id=org.ligi.survivalmanual", "survival") {
@NonNull
@Override
protected Snackbar createSnackBar(final SnackContext snackContext) {
Snackbar snackbar = super.createSnackBar(snackContext);
final TextView textView = (TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text);
textView.setCompoundDrawablesWithIntrinsicBounds(R.mipmap.survival, 0, 0, 0);
textView.setCompoundDrawablePadding(getResources().getDimensionPixelOffset(R.dimen.rhythm));
return snackbar;
}
}.overrideTitleText("Other App by ligi:\nFree Offline Survival Guide")
.overrideActionText("Get It!")
.withDuration(BaseSnack.DURATION_INDEFINITE)
.withConditions(new AfterNumberOfOpportunities(7), new IsConnectedViaWiFiOrUnknown()))
.build()
.engageWhenAppropriate();
}
drawerToggle = new ActionBarDrawerToggle(this, drawer, R.string.drawer_open, R.string.drawer_close);
drawer.addDrawerListener(drawerToggle);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
adapter = new PassTopicFragmentPagerAdapter(passStore.getClassifier(), getSupportFragmentManager());
viewPager.setAdapter(adapter);
if (adapter.getCount() > 0) {
viewPager.setCurrentItem(state.getLastSelectedTab());
}
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
}
@Override
public void onPageSelected(final int position) {
state.setLastSelectedTab(position);
supportInvalidateOptionsMenu();
}
@Override
public void onPageScrollStateChanged(final int state) {
}
});
passStore.syncPassStoreWithClassifier(getString(R.string.topic_new));
onPassStoreChangeEvent(null);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
switch (item.getItemId()) {
case R.id.menu_help:
AXT.at(this).startCommonIntent().activityFromClass(HelpActivity.class);
return true;
case R.id.menu_emptytrash:
new AlertDialog.Builder(this).setMessage(getString(R.string.empty_trash_dialog_message))
.setIcon(R.drawable.ic_alert_warning)
.setTitle(getString(R.string.empty_trash_dialog_title))
.setPositiveButton(R.string.emtytrash_label, new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
final PassStoreProjection passStoreProjection = new PassStoreProjection(passStore,
getString(R.string.topic_trash),
null);
for (final Pass pass : passStoreProjection.getPassList()) {
passStore.deletePassWithId(pass.getId());
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onResume() {
super.onResume();
bus.register(this);
adapter.notifyDataSetChanged();
onPassStoreChangeEvent(null);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.menu_emptytrash)
.setVisible((adapter.getCount() > 0) && adapter.getPageTitle(viewPager.getCurrentItem()).equals(getString(R.string.topic_trash)));
return true;
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.activity_pass_list_view, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
drawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
drawerToggle.onConfigurationChanged(newConfig);
}
@Override
protected void onPause() {
bus.unregister(this);
super.onPause();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onPassStoreChangeEvent(PassStoreChangeEvent passStoreChangeEvent) {
adapter.notifyDataSetChanged();
setupWithViewPagerIfNeeded();
supportInvalidateOptionsMenu();
final boolean empty = passStore.getClassifier().getTopicByIdMap().isEmpty();
emptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
tabLayout.setVisibility(empty ? View.GONE : View.VISIBLE);
}
private void setupWithViewPagerIfNeeded() {
if (!areTabLayoutAndViewPagerInSync()) {
tabLayout.setupWithViewPager(viewPager);
}
}
private boolean areTabLayoutAndViewPagerInSync() {
if (adapter.getCount() == tabLayout.getTabCount()) {
for (int i = 0; i < adapter.getCount(); i++) {
final TabLayout.Tab tabAt = tabLayout.getTabAt(i);
if (tabAt == null || !adapter.getPageTitle(i).equals(tabAt.getText())) {
return false;
}
}
return true;
}
return false;
}
private static class PassTopicFragmentPagerAdapter extends FragmentStatePagerAdapter {
private String[] topic_array;
private final PassClassifier passClassifier;
public PassTopicFragmentPagerAdapter(PassClassifier passClassifier, FragmentManager fragmentManager) {
super(fragmentManager);
this.passClassifier = passClassifier;
notifyDataSetChanged();
}
@Override
public void notifyDataSetChanged() {
final Collection<String> topics = passClassifier.getTopics();
topic_array = topics.toArray(new String[topics.size()]);
super.notifyDataSetChanged();
}
@Override
public Fragment getItem(int position) {
return PassListFragment.newInstance(topic_array[position]);
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE; // TODO - return POSITION_UNCHANGED in some cases
}
@Override
public int getCount() {
return topic_array.length;
}
@Override
public CharSequence getPageTitle(int position) {
return topic_array[position];
}
}
}

View file

@ -0,0 +1,314 @@
package org.ligi.passandroid.ui
import android.Manifest
import android.annotation.TargetApi
import android.app.Activity
import android.app.ProgressDialog
import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v4.view.ViewPager
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.app.AlertDialog
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.TextView
import kotlinx.android.synthetic.main.pass_list.*
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.ligi.axt.AXT
import org.ligi.axt.listeners.ActivityFinishingOnClickListener
import org.ligi.passandroid.App
import org.ligi.passandroid.R
import org.ligi.passandroid.events.PassStoreChangeEvent
import org.ligi.passandroid.events.ScanFinishedEvent
import org.ligi.passandroid.events.ScanProgressEvent
import org.ligi.passandroid.helper.PassTemplates
import org.ligi.passandroid.model.PassStoreProjection
import org.ligi.snackengage.SnackContext
import org.ligi.snackengage.SnackEngage
import org.ligi.snackengage.conditions.AfterNumberOfOpportunities
import org.ligi.snackengage.conditions.connectivity.IsConnectedViaWiFiOrUnknown
import org.ligi.snackengage.snacks.BaseSnack
import org.ligi.snackengage.snacks.DefaultRateSnack
import org.ligi.snackengage.snacks.OpenURLSnack
import org.ligi.tracedroid.TraceDroid
import org.ligi.tracedroid.sending.TraceDroidEmailSender
import permissions.dispatcher.NeedsPermission
import permissions.dispatcher.OnNeverAskAgain
import permissions.dispatcher.OnPermissionDenied
import permissions.dispatcher.RuntimePermissions
@RuntimePermissions
class PassListActivity : PassAndroidActivity() {
companion object {
const val VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK = 19
}
private val OPEN_FILE_READ_REQUEST_CODE = 1000
private val drawerToggle by lazy { ActionBarDrawerToggle(this, drawer_layout, R.string.drawer_open, R.string.drawer_close) }
private val adapter by lazy { PassTopicFragmentPagerAdapter(passStore.classifier, supportFragmentManager) }
private val pd by lazy { ProgressDialog(this) }
@Subscribe(threadMode = ThreadMode.MAIN)
fun onScanProgress(event: ScanProgressEvent) {
if (pd.isShowing) {
pd.setMessage(event.message)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onScanFinished(event: ScanFinishedEvent) {
if (pd.isShowing) {
val message = getString(R.string.scan_finished_dialog_text, event.foundPasses.size)
pd.dismiss()
AlertDialog.Builder(this@PassListActivity).setTitle(R.string.scan_finished_dialog_title).setMessage(message).setPositiveButton(android.R.string.ok, null).show()
}
}
@TargetApi(16)
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
@OnNeverAskAgain(Manifest.permission.READ_EXTERNAL_STORAGE)
internal fun showDeniedFor() {
Snackbar.make(fam, "no permission to scan", Snackbar.LENGTH_INDEFINITE).show()
}
@TargetApi(16)
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
fun scan() {
val intent = Intent(this, SearchPassesIntentService::class.java)
startService(intent)
pd.setTitle(R.string.scan_progressdialog_title)
pd.setMessage(getString(R.string.scan_progressdialog_message))
pd.setCancelable(false)
pd.isIndeterminate = true
pd.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.scan_dialog_send_background_button), ActivityFinishingOnClickListener(this))
pd.show()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
PassListActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults)
}
@TargetApi(VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK)
internal fun onAddOpenFileClick() {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*" // tried with octet stream - no use
startActivityForResult(intent, OPEN_FILE_READ_REQUEST_CODE)
} catch (e: ActivityNotFoundException) {
Snackbar.make(fam, "Unavailable", Snackbar.LENGTH_LONG).show()
}
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == OPEN_FILE_READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
val targetIntent = Intent(this, PassImportActivity::class.java)
targetIntent.data = resultData.data
startActivity(targetIntent)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
App.component().inject(this)
setContentView(R.layout.pass_list)
setSupportActionBar(toolbar)
AXT.at(fab_action_open_file).setVisibility(Build.VERSION.SDK_INT >= VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK)
// don't want too many windows in worst case - so check for errors first
if (TraceDroid.getStackTraceFiles().size > 0) {
tracker.trackEvent("ui_event", "send", "stacktraces", null)
if (settings.doTraceDroidEmailSend()) {
TraceDroidEmailSender.sendStackTraces("ligi@ligi.de", this)
}
} else { // if no error - check if there is a new version of the app
tracker.trackEvent("ui_event", "processFile", "updatenotice", null)
SnackEngage.from(fam).withSnack(DefaultRateSnack().withDuration(BaseSnack.DURATION_INDEFINITE)).withSnack(object : OpenURLSnack("market://details?id=org.ligi.survivalmanual", "survival") {
override fun createSnackBar(snackContext: SnackContext): Snackbar {
val snackbar = super.createSnackBar(snackContext)
val textView = snackbar.view.findViewById(android.support.design.R.id.snackbar_text) as TextView
textView.setCompoundDrawablesWithIntrinsicBounds(R.mipmap.survival, 0, 0, 0)
textView.compoundDrawablePadding = resources.getDimensionPixelOffset(R.dimen.rhythm)
return snackbar
}
}.overrideTitleText("Other App by ligi:\nFree Offline Survival Guide").overrideActionText("Get It!").withDuration(BaseSnack.DURATION_INDEFINITE).withConditions(AfterNumberOfOpportunities(7), IsConnectedViaWiFiOrUnknown())).build().engageWhenAppropriate()
}
drawer_layout.addDrawerListener(drawerToggle)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
view_pager.adapter = adapter
if (adapter.count > 0) {
view_pager.currentItem = state.lastSelectedTab
}
view_pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
state.lastSelectedTab = position
supportInvalidateOptionsMenu()
}
override fun onPageScrollStateChanged(state: Int) {
}
})
passStore.syncPassStoreWithClassifier(getString(R.string.topic_new))
onPassStoreChangeEvent(null)
fab_action_create_pass.setOnClickListener {
val pass = PassTemplates.createAndAddEmptyPass(passStore, resources)
fam.collapse()
AXT.at(this).startCommonIntent().activityFromClass(PassEditActivity::class.java)
val newTitle: String
if (tab_layout.selectedTabPosition < 0) {
newTitle = getString(R.string.topic_new)
} else {
newTitle = adapter.getPageTitle(tab_layout.selectedTabPosition).toString()
}
passStore.classifier.moveToTopic(pass, newTitle)
}
fab_action_scan.setOnClickListener {
PassListActivityPermissionsDispatcher.scanWithCheck(this)
fam.collapse()
}
fab_action_demo_pass.setOnClickListener {
AXT.at(this).startCommonIntent().openUrl("http://espass.it/examples")
fam.collapse()
}
fab_action_open_file.setOnClickListener {
onAddOpenFileClick()
}
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.menu_help -> {
AXT.at(this).startCommonIntent().activityFromClass(HelpActivity::class.java)
true
}
R.id.menu_emptytrash -> {
AlertDialog.Builder(this).setMessage(getString(R.string.empty_trash_dialog_message)).setIcon(R.drawable.ic_alert_warning).setTitle(getString(R.string.empty_trash_dialog_title)).setPositiveButton(R.string.emtytrash_label) { dialog, which ->
val passStoreProjection = PassStoreProjection(passStore,
getString(R.string.topic_trash),
null)
for (pass in passStoreProjection.passList) {
passStore.deletePassWithId(pass.id)
}
}.setNegativeButton(android.R.string.cancel, null).show()
true
}
else -> drawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item)
}
override fun onResume() {
super.onResume()
bus.register(this)
adapter.notifyDataSetChanged()
onPassStoreChangeEvent(null)
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
menu.findItem(R.id.menu_emptytrash).isVisible = adapter.count > 0 && adapter.getPageTitle(view_pager.currentItem) == getString(R.string.topic_trash)
return true
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.activity_pass_list_view, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
drawerToggle.syncState()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
drawerToggle.onConfigurationChanged(newConfig)
}
override fun onPause() {
bus.unregister(this)
super.onPause()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPassStoreChangeEvent(passStoreChangeEvent: PassStoreChangeEvent?) {
adapter.notifyDataSetChanged()
setupWithViewPagerIfNeeded()
supportInvalidateOptionsMenu()
val empty = passStore.classifier.topicByIdMap.isEmpty()
emptyView.visibility = if (empty) View.VISIBLE else View.GONE
tab_layout.visibility = if (empty) View.GONE else View.VISIBLE
}
private fun setupWithViewPagerIfNeeded() {
if (!areTabLayoutAndViewPagerInSync()) {
tab_layout.setupWithViewPager(view_pager)
}
}
private fun areTabLayoutAndViewPagerInSync(): Boolean {
if (adapter.count != tab_layout.tabCount) {
return false
}
for (i in 0..adapter.count - 1) {
val tabAt = tab_layout.getTabAt(i)
if (tabAt == null || adapter.getPageTitle(i) != tabAt.text) {
return false
}
}
return true
}
}

View file

@ -0,0 +1,38 @@
package org.ligi.passandroid.ui
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentStatePagerAdapter
import android.support.v4.view.PagerAdapter
import org.ligi.passandroid.model.PassClassifier
class PassTopicFragmentPagerAdapter(private val passClassifier: PassClassifier, fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) {
private lateinit var topic_array: Array<String>
init {
notifyDataSetChanged()
}
override fun notifyDataSetChanged() {
val topics = passClassifier.getTopics()
topic_array = topics.toTypedArray()
super.notifyDataSetChanged()
}
override fun getItem(position: Int): Fragment {
return PassListFragment.newInstance(topic_array[position])
}
override fun getItemPosition(`object`: Any?): Int {
return PagerAdapter.POSITION_NONE // TODO - return POSITION_UNCHANGED in some cases
}
override fun getCount(): Int {
return topic_array.size
}
override fun getPageTitle(position: Int): CharSequence {
return topic_array[position]
}
}