홈화면

Function:

- Recycler View를 활용하여 카페 목록 생성

- 각 카페 목록 클릭 시 적립 화면으로 전환

 

 

public class RecyclerActivity extends AppCompatActivity {

    RecyclerView mRecyclerView;
    MyAdapter myAdapter;

    SharedPreferences preferences;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler);

        mRecyclerView = findViewById(R.id.recyclerView);

        preferences = this.getSharedPreferences("My_Pref", MODE_PRIVATE);

        getMyList();
    }

    private void getMyList() {
        ArrayList<Model> models = new ArrayList<>();

        Model m = new Model();
        m.setTitle("Ediya");
        m.setDescription("ediya Cafe.");
        m.setImg(R.drawable.ediya);
        models.add(m);

        m = new Model();
        m.setTitle("Starbucks");
        m.setDescription("starbucks Cafe.");
        m.setImg(R.drawable.starbucks);
        models.add(m);

        m = new Model();
        m.setTitle("빽다방");
        m.setDescription("bback Cafe.");
        m.setImg(R.drawable.bbackdabang);
        models.add(m);

        m = new Model();
        m.setTitle("Blue bottle");
        m.setDescription("blue_bottle Cafe.");
        m.setImg(R.drawable.bluebottle);
        models.add(m);

        m = new Model();
        m.setTitle("Angel in us");
        m.setDescription("angel_in_us Cafe.");
        m.setImg(R.drawable.angel_in_us);
        models.add(m);

        m = new Model();
        m.setTitle("TomNToms");
        m.setDescription("Tom N Toms Cafe.");
        m.setImg(R.drawable.tomntoms);
        models.add(m);

        m = new Model();
        m.setTitle("Hollys");
        m.setDescription("Hollys Cafe.");
        m.setImg(R.drawable.hollys);
        models.add(m);

        String mSortSetting = preferences.getString("Sort", "ascending");

        if (mSortSetting.equals("ascending")) {
            Collections.sort(models, Model.By_TITLE_ASCENDING);
        }
        else if (mSortSetting.equals("descending")) {
            Collections.sort(models, Model.By_TITLE_DESCENDING);
        }

        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        myAdapter = new MyAdapter(this, models);
        mRecyclerView.setAdapter(myAdapter);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.menu, menu);

        MenuItem item = menu.findItem(R.id.search);

        SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);

        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                myAdapter.getFilter().filter(query);
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {

                myAdapter.getFilter().filter(newText);
                return false;
            }
        });
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.sorting) {
            sortDialog();
            return true;
        }
        if (id == R.id.logout) {
            Logout();
        }

        return super.onOptionsItemSelected(item);
    }

    private void Logout() {
        Intent intent = new Intent(RecyclerActivity.this, MainActivity.class);
        startActivity(intent);
        finish();
        Toast.makeText(RecyclerActivity.this, "성공적으로 로그아웃 하였습니다.", Toast.LENGTH_SHORT).show();
    }
    private void sortDialog() {

        String[] options = {"Ascending", "Descending"};
        AlertDialog.Builder builder = new AlertDialog.Builder(this);

        builder.setTitle("Sort by");
        builder.setIcon(R.drawable.ic_action_sort);

        builder.setItems(options, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (which == 0) {
                    SharedPreferences.Editor editor = preferences.edit();
                    editor.putString("Sort", "ascending");
                    editor.apply();
                    getMyList();
                }

                if (which == 1) {
                    SharedPreferences.Editor editor = preferences.edit();
                    editor.putString("Sort", "descending");
                    editor.apply();
                    getMyList();
                }
            }
        });

        builder.create().show();
    }

}

 

쿠폰 화면

Function:

- Firebase의 데이터베이스에서 값을 읽어와 수만큼 도장 출력(초기 생성시 모두 0)

- 스탬프 적립 클릭 시 Qr코드 생성 및 화면 출력

- 각 카페 별 다른 스탬프의 개수를 가진 쿠폰 출력

 

public class MainpageActivity extends AppCompatActivity {

    private Button createQRBtn;
    private Button scanQRBtn;
    private ImageView coupons[] = new ImageView[10];
    private FirebaseAuth mAuth;
    private FirebaseDatabase firebaseDatabase = FirebaseDatabase.getInstance();
    private DatabaseReference mReference = firebaseDatabase.getReference();
    private ChildEventListener mChild;

    public int count;

    //private ListView listView;
    //private ArrayAdapter<String> adapter;
    //List<Object> Array = new ArrayList<Object>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mainpage);

        createQRBtn = (Button) findViewById(R.id.createQR);
        scanQRBtn = (Button) findViewById(R.id.scanQR);
        mAuth = FirebaseAuth.getInstance();
        initDatabase();

        createQRBtn.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                Intent intent = new Intent(MainpageActivity.this, CreateQR.class); // class 바꾸기
                startActivity(intent);
            }
        });


        coupons[0] = (ImageView) findViewById(R.id.coupon1);
        coupons[1] = (ImageView) findViewById(R.id.coupon2);
        coupons[2] = (ImageView) findViewById(R.id.coupon3);
        coupons[3] = (ImageView) findViewById(R.id.coupon4);
        coupons[4] = (ImageView) findViewById(R.id.coupon5);
        coupons[5] = (ImageView) findViewById(R.id.coupon6);
        coupons[6] = (ImageView) findViewById(R.id.coupon7);
        coupons[7] = (ImageView) findViewById(R.id.coupon8);
        coupons[8] = (ImageView) findViewById(R.id.coupon9);
        coupons[9] = (ImageView) findViewById(R.id.coupon10);

        FirebaseUser user = mAuth.getCurrentUser();

        if (user != null) {
            String uid = user.getUid();
            mReference = firebaseDatabase.getReference().child("users").child(uid).child("cafeName").child("Starbucks"); // 변경값을 확인할 child 이름
            mReference.addValueEventListener(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    //adapter.clear();
                    //FirebaseUser user = mAuth.getCurrentUser();
                    //String uid = user.getUid();
                    for (DataSnapshot messageData : dataSnapshot.getChildren()) {
                        String msg2 = messageData.getValue().toString();
                        count = Integer.parseInt(msg2);
                        if(count >= 10) {
                            count = count % 10;
                            for(int i = 0; i < count; i++)
                            {
                                coupons[i].setImageDrawable(getResources().getDrawable(R.drawable.coupon_normal));
                            }
                        }
                        else {
                            for (int i = 0; i < count; i++) {
                                coupons[i].setImageDrawable(getResources().getDrawable(R.drawable.coffee_stamp));
                            }
                        }// child 내에 있는 데이터만큼 반복합니다.
                    }
                    //adapter.notifyDataSetChanged();

                }

                @Override
                public void onCancelled(DatabaseError databaseError) {

                }
            });
        }
    }

    private void initDatabase() {
        mReference = firebaseDatabase.getReference("log");
        mReference.child("log").setValue("check");

        mChild = new ChildEventListener() {
            @Override
            public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

            }

            @Override
            public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

            }

            @Override
            public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {

            }

            @Override
            public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        };
        mReference.addChildEventListener(mChild);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mReference.removeEventListener(mChild);
    }


}

 

* 생성된 데이터베이스에서 Stamp-number의 값에 따라 출력 도장 개수 변환

 

스탬프 적립

Function:

- 사용자의 정보와 현재 스탬프의 개수가 포함된 Qr코드를 생성한다.

- 생성된 Qr코드를 통해 Firebase Database의 올바른 위치에 접근한다.

- Master App을 통해 Qr코드를 스캔할 시 스탬프 개수가 1 증가

- 증가된 스탬프 개수가 Firebase Database에 동기화

 

public class CreateQR extends AppCompatActivity {
    private ImageView iv;
    private ImageView logo;
    //private TextView text;
    private Button bt;
    private String text;
    StringBuffer temp = new StringBuffer();
    Random rnd = new Random();

    FirebaseAuth mAuth;
    FirebaseDatabase database;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_create_qr);

        StringBuilder textToSend = new StringBuilder(); //qr코드에 담을 데이터
        ActionBar actionBar = getSupportActionBar();
        actionBar.setTitle("스탬프 적립");

        Intent intent = getIntent();
        Bundle bundle = intent.getExtras();
        String cafe_name = bundle.getString("cafe_name");


        byte[] mBytes = bundle.getByteArray("logo");
        Bitmap logo_img = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length);


        for (int i = 0; i < 20; i++) {
            int rIndex = rnd.nextInt(3);
            switch (rIndex) {
                case 0:
                    // a-z
                    temp.append((char) ((int) (rnd.nextInt(26)) + 97));
                    break;
                case 1:
                    // A-Z
                    temp.append((char) ((int) (rnd.nextInt(26)) + 65));
                    break;
                case 2:
                    // 0-9
                    temp.append((rnd.nextInt(10)));
                    break;
            }
        } //qr코드 고유 시리얼넘버 지정

        iv = (ImageView)findViewById(R.id.qrcode);
        mAuth = FirebaseAuth.getInstance();
        database = FirebaseDatabase.getInstance();
        bt = (Button) findViewById(R.id.coupon_bt);
        logo = (ImageView) findViewById(R.id.imageView);

        logo.setImageBitmap(logo_img);

        FirebaseUser user = mAuth.getCurrentUser();
        if (user != null) {
            // Name, email address, and profile photo Url
            //String Serial_num = new String(temp); //시리얼넘버
            String uid = user.getUid(); //사용자uid
            textToSend.append(uid + "/" + cafe_name);
            text = textToSend.toString();
            try {
                text = new String(text.getBytes("UTF-8"), "ISO-8859-1");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }

        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
        try{
            BitMatrix bitMatrix = multiFormatWriter.encode(text, BarcodeFormat.QR_CODE,200,200);
            BarcodeEncoder barcodeEncoder = new BarcodeEncoder();
            Bitmap bitmap = barcodeEncoder.createBitmap(bitMatrix);
            iv.setImageBitmap(bitmap);
        }catch (Exception e){}

        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(CreateQR.this, RecyclerActivity.class);
                startActivity(intent);
            }
        });
    }
}

 

Firebase Database와 동기화된 쿠폰 화면

*아래와 같이 Stamp의 number가 5일 경우 쿠폰 화면에 도장이 5개 적립

 

 

각 카페별 도장 개수 확인

* 아래와 같이 Starbucks에 9개, TomNToms에 3개, 빽다방에 5개의 도장이 적립되어 있는지 확인한다.

 

Firebase의 Database와 잘 연동되는걸 확인할 수 있다.


통합 코드는 https://github.com/thdalstn6352/MyCouponWallet 참조

Client App scenario

1. Firebase를 이용하여 회원가입과 로그인화면을 구현한다.

2. 로그인에 성공할 경우 저장된 카페 목록이 나타난다.

3. 각 카페별 다른 스탬프 개수를 가진다.

4. 스탬프 적립을 원할 경우 스탬프 적립 버튼을 통해 Qr코드를 생성한다.

5. Master App의 Qr scan기능을 활용하여 Client의 스탬프를 적립한다.

6. 변경된 스탬프의 개수를 Firebase에 수정한다.

7. Client App화면에 변경된 스탬프의 개수가 출력된다.

 

시작 화면 

Function:

- 3초간 splash 이미지가 실행된 후 메인 화면으로 넘어간다.

 

public class Splashscreen extends Activity implements Animation.AnimationListener {
    private static int SPLASH_TIME_OUT = 3000;
    private ImageView mImgViewLogo;
    private Animation anim;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splashscreen);
        mImgViewLogo = findViewById(R.id.imgViewSplashLogo);
        anim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.fade_in);
        anim.setAnimationListener(this);
        mImgViewLogo.startAnimation(anim);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                startActivity(new Intent(Splashscreen.this, MainActivity.class));
                finish();
            }
        }, SPLASH_TIME_OUT);
    }

    @Override
    public void onAnimationStart(Animation animation) {   
    }
    @Override
    public void onAnimationEnd(Animation animation) {
    }
    @Override
    public void onAnimationRepeat(Animation animation) {
    }
}

 

로그인 화면

Function:

- 이메일과 비밀번호 항목에 입력한 값이 Firebase Auth에 저장되어 있는 값과 같을 경우 로그인성공

- 로그인 실패시 오류 메시지 출력

- 계정 만들기 클릭시 회원가입 화면으로 전환

 

public class MainActivity extends AppCompatActivity {

    Button login_button;
    TextView alert_message;
    TextView make_account;
    EditText Edit_email;
    EditText Edit_pw;
    FirebaseAuth mAuth;
    
    @Override
    	protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final Animation alertMessageAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.alert_message_animation);
        
        mAuth = FirebaseAuth.getInstance();
        Edit_email = (EditText) findViewById(R.id.input_email);
        Edit_pw = (EditText) findViewById(R.id.input_pw);
        login_button = (Button) findViewById(R.id.login_button);
        login_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String email = Edit_email.getText().toString().trim();
                String pw = Edit_pw.getText().toString().trim();

                final String TAG = "Login_Activity";

                if ((email != null && !email.isEmpty()) && (pw != null && !pw.isEmpty())) {
                    mAuth.signInWithEmailAndPassword(email, pw).addOnCompleteListener(MainActivity.this, new OnCompleteListener<AuthResult>() {
                        @Override
                        public void onComplete(@NonNull Task<AuthResult> task) {
                            if (task.isSuccessful()) {
                                FirebaseUser user = mAuth.getCurrentUser();
                                Toast.makeText(MainActivity.this, "로그인에 성공하였습니다.",
                                        Toast.LENGTH_SHORT).show();
                                Intent intent = new Intent(MainActivity.this, RecyclerActivity.class); // class 변경하기
                                startActivity(intent);
                            }
                            else {
                                alert_message = (TextView) findViewById(R.id.alert_message);
                                alert_message.startAnimation(alertMessageAnim);
                            }}});
              }}});

        make_account = (TextView) findViewById(R.id.make_account);
        make_account.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, AccountActivity.class);
                startActivity(intent);
            }});
        }}

 

회원가입 화면

Function:

- 각 항목에 올바른 데이터를 입력 후 회원가입 버튼 클릭시 Firebase Auth에 데이터 저장

- 이메일 형식이 아니거나 두 비밀번호가 다를경우 회원가입 실패 메시지 출력

- 이용약관 클릭시 해당 문서 출력(구현 x)

 

public class AccountActivity extends AppCompatActivity {

    Button sign_button;
    EditText name_input;
    EditText email_input;
    EditText pw_input;
    EditText pw_reinput;
    FirebaseAuth mAuth;
    FirebaseDatabase database;
    ImageView setImage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_account);

        name_input = (EditText) findViewById(R.id.sign_up_name);
        email_input = (EditText) findViewById(R.id.sign_up_email);
        pw_input = (EditText) findViewById(R.id.sign_up_pw);
        pw_reinput = (EditText) findViewById(R.id.pw_rewrite);
        setImage = (ImageView) findViewById(R.id.setImage);
        mAuth = FirebaseAuth.getInstance();
        database =  FirebaseDatabase.getInstance();
        pw_reinput.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if(pw_input.getText().toString().equals(pw_reinput.getText().toString())) {
                    setImage.setImageResource(R.drawable.correct);
                }
                else {
                    setImage.setImageResource(R.drawable.error);
                }
            }
            @Override
            public void afterTextChanged(Editable s) {
            }
        });
        sign_button = (Button) findViewById(R.id.sign_button);
        sign_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String email = email_input.getText().toString().trim();
                String pw = pw_input.getText().toString().trim();
                final String name = name_input.getText().toString().trim();

                if(!android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()){
                    Toast.makeText(AccountActivity.this,"이메일 형식이 아닙니다",Toast.LENGTH_SHORT).show();
                }

                if ((email != null && !email.isEmpty()) && (pw != null && !pw.isEmpty())) {
                    mAuth.createUserWithEmailAndPassword(email, pw)
                            .addOnCompleteListener(AccountActivity.this, new OnCompleteListener<AuthResult>() {
                                @Override
                                public void onComplete(@NonNull Task<AuthResult> task) {
                                    UserModel userModel = new UserModel();
                                    CafeModel cafeModel = new CafeModel();
                                    if (task.isSuccessful()) {
                                        userModel.userName = name;
                                        userModel.userEmail = email;

                                        String uid = task.getResult().getUser().getUid();
                                        database.getReference().child("users").child(uid).setValue(userModel);
                                        database.getReference().child("users").child(uid).child("cafeName").child("Ediya").child("Stamp").child("number").setValue(0);
                                        database.getReference().child("users").child(uid).child("cafeName").child("Angel in us").child("Stamp").child("number").setValue(0);
                                        database.getReference().child("users").child(uid).child("cafeName").child("Blue bottle").child("Stamp").child("number").setValue(0);
                                        database.getReference().child("users").child(uid).child("cafeName").child("Hollys").child("Stamp").child("number").setValue(0);
                                        database.getReference().child("users").child(uid).child("cafeName").child("Starbucks").child("Stamp").child("number").setValue(0);
                                        database.getReference().child("users").child(uid).child("cafeName").child("TomNToms").child("Stamp").child("number").setValue(0);
                                        database.getReference().child("users").child(uid).child("cafeName").child("빽다방").child("Stamp").child("number").setValue(0);

                                        Toast.makeText(getApplicationContext(), "회원가입이 완료되었습니다.", Toast.LENGTH_SHORT).show();

                                        Intent intent = new Intent(AccountActivity.this, MainActivity.class);
                                        startActivity(intent);
                                    } else {
                                        Toast.makeText(getApplicationContext(), "회원가입이 실패하였습니다.", Toast.LENGTH_SHORT).show();
                                    }}});
                }}});
    }}

 

* 회원가입 완료 후 Firebase의 Authentication 화면

 

 

* Firebase의 Database

프로젝트 목적

 

- 당장 학교 앞에만 해도 스타벅스, 이디야, 빽다방같은 대기업 카페를 제외하고는 모두 아날로그 쿠폰을 사용하고있다.

하지만 학교내에는 영세업자들이 운영하는 개인카페의 수가 더욱 많다. 영세업자들은 자신의 카페만을 위한 어플 제작 및 운용 비용의 부담으로 인해 아날로그 형태의 쿠폰을 사용하고 있다.

 

따라서 개인 카페를 운영하는 영세업자들을 위한 무료 통합 쿠폰 관리 어플을 만들어 사용자가 쉽게 적립과 쿠폰 사용기능을 이용할 수 있도록 하는 것이 목표이다 

 

 

사용 Tool

 

-Android Studio

앱 구성 및 디자인

 

-Firebase

로그인/회원가입 기능, 데이터베이스

 

 

예상 구현 기능

 

-Client App

  • 데이터베이스에 저장되어 있는 Stamp 수를 가져와 각 카페별로 도장 적립
  • 스탬프 적립을 희망할 시 QR코드 생성
  • 스탬프의 수가 10개가 될 경우 자동으로 쿠폰 생성(유효기간 존재) 

 

- Master App

  • QR코드 스캔 및 적립
  • 사용자 정보 확인
  • 발행 쿠폰 확인

통합 코드는 https://github.com/thdalstn6352/MyCouponWallet 참조

+ Recent posts